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.

Brandon Kelly

April 26, 2017

More Decks by Brandon Kelly

Other Decks in Technology


  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