Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Craft 3 Plugin Development

Craft 3 Plugin Development

A look at some of the under-the-hood changes coming in Craft CMS 3.

Avatar for Brandon Kelly

Brandon Kelly

April 26, 2017
Tweet

More Decks by Brandon Kelly

Other Decks in Technology

Transcript

  1. Three Goals for Cra! 3 1. Establish new coding guidelines

    to improve performance, stability, and maintainability 2. Support modern dev toolkits 3. Stop reinventing the wheel
  2. Big Changes — Built on Yii 2 — Requires PHP

    7 — PostgreSQL support — Tons of refactoring — More extensible
  3. Yii 2 — Complete rewrite — Code style based on

    PSR-1 and PSR-2 specs — Namespaces and class autoloading based on PSR-4 spec — Console requests are a first class citizen now — Asset bundles — Query builder
  4. PSR-4 Namespaces and Autoloading — Each package gets a root

    namespace that maps to a source directory — Craft’s is craft\ — 1st party plugins use craft\something\ — 3rd parties should use dev_name\something\ — Class namespaces reflect where they live — Class names reflect their filename
  5. PSR-4 Namespaces and Autoloading Class Location in vendor/ craft\web\View cra!cms/cms/src/web/

    View.php craft\awss3\Volume cra!cms/aws-s3/src/ Volume.php marionnewlevant\snitch\ Plugin marionnewlevant/snitch/ src/Plugin.php
  6. Web vs. Console Requests Thing Return value for web requests

    Craft::$app craft\web\ Application ->request craft\web\Request ->isConsoleRequest false
  7. Web vs. Console Requests Thing Return value for console requests

    Craft::$app craft\console\ Application ->request craft\console\Request ->isConsoleRequest true
  8. Asset Bundles — Classes that list CSS and JavaScript dependencies,

    which can be registered to the View when needed — They can list other asset bundles as dependencies, too — Registered asset bundles get published into a public directory, so they can be served statically
  9. Query Builder — All SELECT queries are now built from

    yii\db\Query objects (even Active Record queries and Element queries) — Lots of handy execution functions — DB-agnostic
  10. Main New Features Since PHP 5.3 — Traits (5.4) —

    ['a' => 1] short array syntax (5.4) — ::class keyword (5.5) — Scalar argument type declarations (7.0) — Return type declarations (7.0) — ?? “null coalescing operator” (7.0)
  11. Component Refactor — In Craft 2, components are represented by

    two objects: the model and the ’type — The model stores common attributes (ID, name, handle, etc.) — The ’type handles all type-specific functionality (settings, view HTML, etc.)
  12. Component Refactor — In Craft 3, components are represented by

    a single model — Common attributes and default functionality are defined in a base class — Type-specific attributes/functionality is handled by subclasses
  13. Component Refactor — Interface-oriented APIs — ElementInterface, ElementActionInterface, FieldInterface, and

    WidgetInterface — Common attributes provided by traits — ElementTrait, FieldTrait, and WidgetTrait
  14. Example: the Feed widget — craft\base\WidgetInterface defines required widget methods

    — craft\base\WidgetTrait defines required widget properties — craft\base\Widget implements them as much as it can, providing some default functionality — craft\widgets\Feed extends the base class, filling in the gaps and overriding default assumptions when needed
  15. Speed Boost — Stop using magic getters and setters —

    Class autoloading instead of file scanning — Explicit plugin service registration — Event-based ’type registration — Events instead of hooks — Optimized element queries
  16. How we’ve avoided magic getters and setters: — No more

    defineAttributes() for model attributes; use public properties instead — Config settings stored on class properties now (GeneralConfig, DbConfig, etc.) — Custom field values stored on public properties of dynamically-generated ContentBehavior
  17. use yii\base\Event; use craft\services\Fields; class Plugin extends \craft\base\Plugin { public

    function init() { Event::on(Fields::class, /*event*/, /*callback*/); } }
  18. use yii\base\Event; use craft\services\Fields; class Plugin extends \craft\base\Plugin { public

    function init() { Event::on(Fields::class, Fields::EVENT_REGISTER_FIELD_TYPES, /*callback*/); } }
  19. use yii\base\Event; use craft\services\Fields; use craft\events\RegisterComponentTypesEvent; class Plugin extends \craft\base\Plugin

    { public function init() { Event::on(Fields::class, Fields::EVENT_REGISTER_FIELD_TYPES, function(RegisterComponentTypesEvent $e) { $e->types[] = MyFieldType::class; }); } }
  20. How we optimized element queries: — All Conditions applied in

    a sub-query — Ordering and limiting applied in the sub-query — All the actual returned data is selected by the main query
  21. SELECT elements.id, elements.fieldLayoutId, elements.uid, elements.enabled, elements.archived, elements.dateCreated, elements.dateUpdated, elements_i18n.slug, elements_i18n.uri,

    elements_i18n.enabled AS enabledForSite, entries.sectionId, entries.typeId, entries.authorId, entries.postDate, entries.expiryDate, content.id AS contentId, content.title, content.field_body FROM ( SELECT elements.id AS elementsId, elements_i18n.id AS elementsI18nId, content.id AS contentId FROM elements INNER JOIN entries ON entries.id = elements.id INNER JOIN elements_i18n ON elements_i18n.elementId = elements.id INNER JOIN content ON content.elementId = elements.id WHERE elements_i18n.siteId=1 AND content.siteId=1 AND elements_i18n.enabled='1' ORDER BY postDate DESC LIMIT 100 ) subquery INNER JOIN entries ON entries.id = subquery.elementsId INNER JOIN elements ON elements.id = subquery.elementsId INNER JOIN elements_i18n ON elements_i18n.id = subquery.elementsI18nId INNER JOIN content ON content.id = subquery.contentId ORDER BY postDate DESC
  22. Stability and Maintainability — Doc blocks for IDE code completion

    — Declare argument/return types when possible — Call getter/setter methods directly rather than going through magic __get() and __set() — Clarity over brevity/cleverness — Utilize PhpStorm inspections and Scrutinizer
  23. Composer — No more plugin dependency conflicts — Plugins can

    share custom helper classes, etc, with packages
  24. Guzzle 6 + PSR-7 — Create PSR-7-compliant HTTP clients with

    Craft::createGuzzleClient() — Configured from config/guzzle.php — Used when sending support requests, checking for updates, fetching RSS feeds — Amazon S3 and Mailgun plugins use it
  25. Modules… — are sub-applications of the main app — have

    their own application components/services — have their own web/console controllers — can be nested
  26. Plugins extend Modules with… — the concept of installation/uninstallation —

    their own DB migration tracks — their own translation category
  27. The training wheels are off. — No more automatic table

    creation/deletion on install/ uninstall — No more automatic service registration — No more automatic component registration
  28. Plugins aren’t always needed anymore. — Bootstrap custom modules in

    config/app.php — Event-based route definition (craft\web\UrlManager::EVENT_REGISTER_CP_URL_RULES) — Use Content Migrations to make DB changes — Event-based component registration
  29. Definitely make it a plugin if… — you want to

    make it available in the Plugin Store — you could use the ability to install/uninstall
  30. Maybe skip the plugin if… — it’s just for this

    one project — it’s a shared library that is really just a dependency of actual plugins