Autoloading și Namespaces în Plugin-uri și Teme WordPress: Pot funcționa?

30 aug. 2012, 23:00:45
Vizualizări: 29.2K
Voturi: 83

A folosit cineva autoloading și/sau namespace-uri PHP în cadrul unui plugin sau unei teme?

Ce părere aveți despre utilizarea lor? Există vreun dezavantaj? Capcane?

Notă: namespace-urile sunt disponibile doar în PHP 5.3+. Să presupunem, pentru această întrebare, că știți că veți lucra cu servere care au PHP 5.3 sau o versiune mai nouă.

0
Toate răspunsurile la întrebare 3
0
100

Bine, am avut două proiecte mari în care am avut controlul suficient asupra serverului pentru a folosi namespace-uri și autoloading.

În primul rând. Autoloading-ul este minunat. Să nu te mai îngrijorezi de require-uri este un lucru relativ bun.

Iată un loader pe care l-am folosit la câteva proiecte. Verifică mai întâi dacă clasa se află în namespace-ul curent, apoi renunță dacă nu. De acolo, este doar o manipulare de string-uri pentru a găsi clasa.

<?php
spl_autoload_register(__NAMESPACE__ . '\\autoload');
function autoload($cls)
{
    $cls = ltrim($cls, '\\');
    if(strpos($cls, __NAMESPACE__) !== 0)
        return;

    $cls = str_replace(__NAMESPACE__, '', $cls);

    $path = PLUGIN_PATH_PATH . 'inc' . 
        str_replace('\\', DIRECTORY_SEPARATOR, $cls) . '.php';

    require_once($path);
}

Acest lucru poate fi adaptat ușor pentru utilizare fără namespace-uri. Presupunând că prefixezi clasele plugin-ului/temei în mod uniform, poți testa pur și simplu acel prefix. Apoi folosește underscore-uri în numele clasei ca substituenți pentru separatorii de directoare. Dacă folosești multe clase, probabil vei dori să folosești un autoloader bazat pe classmap.

Namespace-uri și Hook-uri

Sistemul de hook-uri al WordPress funcționează folosind call_user_func (și call_user_func_array), care ia numele funcțiilor ca string-uri și le apelează când este făcută apelarea funcției do_action (și, ulterior, call_user_func).

Cu namespace-uri, asta înseamnă că va trebui să transmiți nume de funcții complet calificate care includ namespace-ul în hook-uri.

<?php
namespace WPSE\SomeNameSpace;

add_filter('some_filter', 'WPSE\\SomeNameSpace\\the_function');
function the_function()
{
   return 'did stuff';
}

Probabil ar fi mai bine să folosești constanta magică __NAMESPACE__ în mod liberal dacă vrei să faci asta.

<?php
namespace WPSE\SomeNameSpace;

add_filter('some_filter', __NAMESPACE__ . '\\the_function');
function the_function()
{
   return 'did stuff';
}

Dacă pui întotdeauna hook-urile în clase, este mai ușor. Crearea standard a unei instanțe a unei clase și a tuturor hook-urilor în constructor cu $this funcționează bine.

<?php
namespace WPSE\SomeNameSpace;

new Plugin;

class Plugin
{
    function __construct()
    {
        add_action('plugins_loaded', array($this, 'loaded'));
    }

    function loaded()
    {
        // asta funcționează!
    }
}

Dacă folosești metode statice așa cum vreau eu să fac, va trebui să transmiți numele complet calificat al clasei ca prim argument al array-ului. Asta e mult de lucru, așa că poți folosi pur și simplu constanta magică __CLASS__ sau get_class.

<?php
namespace WPSE\SomeNameSpace;

Plugin::init();

class Plugin
{
    public static function init()
    {
        add_action('plugins_loaded', array(__CLASS__, 'loaded'));
        // SAU: add_action('plugins_loaded', array(get_class(), 'loaded'));
    }

    public static function loaded()
    {
        // asta funcționează!
    }
}

Folosirea claselor din Nucleu

Rezoluția numelor de clase în PHP este un pic ciudată. Dacă vei folosi clase din nucleul WordPress (WP_Widget în exemplul de mai jos), trebuie să furnizezi declarații use.

use \WP_Widget;

class MyWidget extends WP_Widget
{
   // ...
}

Sau poți folosi numele complet calificat al clasei - practic doar prefixându-l cu un backslash.

<?php
namespace WPSE\SomeNameSpace;

class MyWidget extends \WP_Widget
{
   // ...
}

Definirea constantelor

Acest lucru este mai general în PHP, dar m-a afectat, așa că îl menționez aici.

Poate vei dori să definești lucruri pe care le vei folosi des, cum ar fi calea către plugin-ul tău. Folosirea instrucțiunii define pune lucrurile în namespace-ul root, decât dacă transmiți explicit namespace-ul în primul argument al define.

<?php
namespace WPSE\SomeNameSpace;

// în namespace-ul root
define('WPSE_63668_PATH', plugin_dir_path(__FILE__));

// în namespace-ul curent
define(__NAMESPACE__ . '\\PATH', plugin_dir_path(__FILE__));

Poți folosi și cuvântul-cheie const la nivelul root al unui fișier cu PHP 5.3+. Constantele sunt întotdeauna în namespace-ul curent, dar sunt mai puțin flexibile decât un apel define.

<?php
namespace WPSE\SomeNameSpace;

// în namespace-ul curent
const MY_CONST = 1;

// asta nu va funcționa!
const MY_PATH = plugin_dir_path(__FILE__);

Te rog să adaugi orice alte sfaturi pe care le-ai putea avea!

27 sept. 2012 23:08:44
4
20

Iată un răspuns din 2017.

Autoloading-ul este minunat. Namespacing-ul este minunat.

Deși poți să-l implementezi singur, în 2017 cel mai logic este să folosești magnificul și omniprezentul Composer pentru a gestiona cerințele tale PHP. Composer suportă atât autoloading PSR-0 cât și PSR-4, dar primul a fost depreciat din 2014, așa că folosește PSR-4. Acesta reduce complexitatea directorilor tăi.

Noi păstrăm fiecare dintre plugin-urile/temele noastre în propriul lor repository Github, fiecare cu propriul lor fișier composer.json și composer.lock.

Iată structura de directoare pe care o folosim pentru plugin-urile noastre. (Nu avem cu adevărat un plugin numit awesome-plugin, dar ar trebui.)

plugins/awesome-plugin/bootstrap.php
plugins/awesome-plugin/composer.json
plugins/awesome-plugin/composer.lock
plugins/awesome-plugin/awesome-plugin.php
plugins/awesome-plugin/src/*

plugins/awesome-plugin/vendor/autoload.php
plugins/awesome-plugin/vendor/*

Dacă furnizezi un fișier composer.json adecvat, Composer se ocupă de namespacing și autoloading aici.

{
    "name": "awesome-company/awesome-plugin",
    "description": "Wordpress plugin for AwesomeCompany website, providing awesome functionality.",
    "type": "wordpress-plugin",
    "autoload": {
        "psr-4": {
            "AwesomeCompany\\Plugins\\AwesomePlugin\\": "src"
        }
    }
}

Când rulezi composer install, acesta creează directorul vendor și fișierul vendor/autoload.php, care va încărca automat toate fișierele tale cu namespace în src/, precum și orice alte biblioteci pe care ai putea să le ceri.

Apoi, în partea de sus a fișierului principal al plugin-ului (care pentru noi este awesome-plugin.php), după metadatele plugin-ului, ai nevoie doar de:

// Autoloading Composer.
require_once __DIR__ . '/vendor/autoload.php';

...

Funcționalitate Bonus

Nu este o necesitate, dar noi folosim boilerplate-ul Wordpress Bedrock pentru a folosi Composer de la bun început. Apoi putem folosi Composer pentru a asambla plugin-urile de care avem nevoie prin Composer, inclusiv propriul tău plugin pe care l-ai scris mai sus. În plus, datorită WPackagist, poți cere orice alt plugin de la Wordpress.org (vezi exemplul cu cool-theme și cool-plugin mai jos).

{
  "name": "awesome-company/awesome-website",
  "type": "project",
  "license": "proprietary",
  "description": "WordPress boilerplate with modern development tools, easier configuration, and an improved folder structure",
  "config": {
    "preferred-install": "dist"
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://wpackagist.org"
    },
    { // Spune Composer-ului să caute plugin-ul nostru Awesome aici.
        "url": "https://github.com/awesome-company/awesome-plugin.git",
        "type": "git"
    }
  ],
  "require": {
    "php": ">=5.5",
    "awesome-company/awesome-plugin": "dev-production", // Plugin-ul nostru!
    "wpackagist-plugin/cool-plugin": "dev-trunk",       // Plugin-ul altcuiva
    "wpackagist-theme/cool-theme": "dev-trunk",         // Tema altcuiva
    "composer/installers": "~1.2.0",     // Implicit Bedrock
    "vlucas/phpdotenv": "^2.0.1",        // Implicit Bedrock
    "johnpbloch/wordpress": "4.7.5",     // Implicit Bedrock
    "oscarotero/env": "^1.0",            // Implicit Bedrock
    "roots/wp-password-bcrypt": "1.0.0"  // Implicit Bedrock
  },
  "extra": {
    // Aceasta este magia care plasează pachetele cu TYPE-ul corect în locația corectă. 
    "installer-paths": {
      "web/app/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
      "web/app/plugins/{$name}/": ["type:wordpress-plugin"],
      "web/app/themes/{$name}/": ["type:wordpress-theme"]
    },
    "wordpress-install-dir": "web/wp"
  },
  "scripts": {
    "test": [
      "vendor/bin/phpcs"
    ]
  }
}

Notă 1: Comentariile nu sunt permise în JSON, dar am adnotat fișierul de mai sus pentru mai multă claritate.

Notă 2: Am eliminat unele părți din fișierul boilerplate Bedrock pentru concizie.

Notă 3: Acesta este motivul pentru care câmpul type din primul fișier composer.json este semnificativ. Composer îl plasează automat în directorul web/app/plugins.

7 iun. 2017 03:34:02
Comentarii

Apreciez răspunsul tău, foarte util! Dar sunt curios despre "bootstrap.php" la care te referi. Ce conține? :)

INT INT
22 sept. 2018 23:15:53

Folosirea unui fișier bootstrap.php este o alegere stilistică pe care o fac în majoritatea proiectelor mele, indiferent dacă sunt în WordPress sau nu. Bootstrap-ul meu verifică în mod normal setările și variabilele de mediu; scopul său principal este să se asigure că plugin-ul meu are întotdeauna tot ce-i trebuie pentru a rula, indiferent dacă rulează în interiorul WordPress sau ca o aplicație PHP standalone.

haz haz
24 sept. 2018 01:43:25

Salut, unde rulez comanda `composer install`?

LΞИIИ LΞИIИ
27 apr. 2020 08:13:09

@LeninZapata Ar trebui să rulezi composer install în directorul care conține fișierul composer.json. De exemplu, dacă clonezi Bedrock (https://github.com/roots/bedrock), atunci intră în directorul bedrock și rulează comanda acolo.

haz haz
6 mai 2020 09:43:54
0

Folosesc autoloading (deoarece pluginul meu are multe clase - parțial pentru că include Twig), niciodată nu am avut probleme semnalate (plugin instalat de peste 20.000 de ori).

Dacă ești sigur că nu vei avea niciodată nevoie să folosești o instalare PHP care nu suportă namespaces, atunci ești în regulă (~70% din blogurile WordPress actuale nu suportă namespaces). Câteva lucruri de reținut:

Îmi amintesc că namespaces nu sunt case sensitive în PHP obișnuit, dar sunt când folosești FastCGI PHP pe IIS - asta poate cauza probleme dacă testezi pe Linux și nu observi o literă mică unde nu trebuie.

De asemenea, chiar dacă ești sigur că codul pe care îl dezvolți acum va fi folosit doar pe versiuni > 5.3.0, nu vei putea refolosi acel cod în proiecte care nu au acest lux - acesta este motivul principal pentru care nu am folosit namespaces în proiectele interne. Am descoperit că namespaces nu aduc atât de multă valoare în comparație cu posibila bătaie de cap de a trebui să elimini dependența de ele.

3 sept. 2012 17:07:31