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
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
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
Web vs. Console Requests Thing Return value for console requests Craft::$app craft\console\ Application ->request craft\console\Request ->isConsoleRequest true
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
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
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.)
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
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
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
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
use yii\base\Event; use craft\services\Fields; class Plugin extends \craft\base\Plugin { public function init() { Event::on(Fields::class, /*event*/, /*callback*/); } }
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*/); } }
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; }); } }
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
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
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
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
Modules… — are sub-applications of the main app — have their own application components/services — have their own web/console controllers — can be nested
The training wheels are off. — No more automatic table creation/deletion on install/ uninstall — No more automatic service registration — No more automatic component registration