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
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
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
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.)
a single model — Common attributes and default functionality are defined in a base class — Type-specific attributes/functionality is handled by subclasses
— 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
Class autoloading instead of file scanning — Explicit plugin service registration — Event-based ’type registration — Events instead of hooks — Optimized element queries
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
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
— 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
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
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