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


  1. None
  2. 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
  3. Big Changes — Built on Yii 2 — Requires PHP

    7 — PostgreSQL support — Tons of refactoring — More extensible
  4. Yii 2

  5. 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
  6. 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
  7. 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
  8. Web vs. Console Requests Thing Return value for web requests

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

    Craft::$app craft\console\ Application ->request craft\console\Request ->isConsoleRequest true
  10. 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
  11. 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
  12. Normal query example: $admins = (new \craft\db\Query()) ->from('{{%users}} u') ->innerJoin('{{%elements}}

    e', '[[e.id]]=[[u.id]]') ->where(['admin' => 1]) ->all();
  13. Active Record query example: $admins = \craft\records\User::find() ->with('element') ->where(['admin' =>

    1]) ->all();
  14. Element Query example: $admins = \craft\elements\User::find() ->admin() ->all();

  15. PHP 7

  16. 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)
  17. Refactoring

  18. 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.)
  19. 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
  20. Component Refactor — Interface-oriented APIs — ElementInterface, ElementActionInterface, FieldInterface, and

    WidgetInterface — Common attributes provided by traits — ElementTrait, FieldTrait, and WidgetTrait
  21. 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
  22. 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
  23. 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
  24. “Event-based component registration” example…

  25. use yii\base\Event; class Plugin extends \craft\base\Plugin { public function init()

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

    function init() { Event::on(Fields::class, /*event*/, /*callback*/); } }
  27. 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*/); } }
  28. 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; }); } }
  29. 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
  30. 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
  31. 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
  32. Php Inspections Plugin

  33. Yii2 Inspections Plugin

  34. github.com/craftcms/phpstorm-settings

  35. Extensibility Improvements

  36. Composer — No more plugin dependency conflicts — Plugins can

    share custom helper classes, etc, with packages
  37. Example: Vue.js Asset Bundle

  38. New Component Types — Volumes — Utilities — Mailer adapters

  39. 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
  40. Plugins

  41. Plugins are an extension of Yii Modules in Craft 3.

  42. Modules… — are sub-applications of the main app — have

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

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

    creation/deletion on install/ uninstall — No more automatic service registration — No more automatic component registration
  45. 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
  46. Definitely make it a plugin if… — you want to

    make it available in the Plugin Store — you could use the ability to install/uninstall
  47. 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