$30 off During Our Annual Pro Sale. View Details »

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
Tweet

More Decks by Brandon Kelly

Other Decks in Technology

Transcript

  1. View Slide

  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

    View Slide

  3. Big Changes
    — Built on Yii 2
    — Requires PHP 7
    — PostgreSQL support
    — Tons of refactoring
    — More extensible

    View Slide

  4. Yii 2

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  12. Normal query example:
    $admins = (new \craft\db\Query())
    ->from('{{%users}} u')
    ->innerJoin('{{%elements}} e',
    '[[e.id]]=[[u.id]]')
    ->where(['admin' => 1])
    ->all();

    View Slide

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

    View Slide

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

    View Slide

  15. PHP 7

    View Slide

  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)

    View Slide

  17. Refactoring

    View Slide

  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.)

    View Slide

  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

    View Slide

  20. Component Refactor
    — Interface-oriented APIs
    — ElementInterface,
    ElementActionInterface,
    FieldInterface,
    and WidgetInterface
    — Common attributes provided by traits
    — ElementTrait, FieldTrait, and WidgetTrait

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  24. “Event-based component registration” example…

    View Slide

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

    View Slide

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

    View Slide

  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*/);
    }
    }

    View Slide

  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;
    });
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  32. Php Inspections Plugin

    View Slide

  33. Yii2 Inspections Plugin

    View Slide

  34. github.com/craftcms/phpstorm-settings

    View Slide

  35. Extensibility Improvements

    View Slide

  36. Composer
    — No more plugin dependency conflicts
    — Plugins can share custom helper classes, etc, with
    packages

    View Slide

  37. Example: Vue.js Asset Bundle

    View Slide

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

    View Slide

  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

    View Slide

  40. Plugins

    View Slide

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

    View Slide

  42. Modules…
    — are sub-applications of the main app
    — have their own application components/services
    — have their own web/console controllers
    — can be nested

    View Slide

  43. Plugins extend Modules with…
    — the concept of installation/uninstallation
    — their own DB migration tracks
    — their own translation category

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide