From 75a6a188e46b8ca89588ed9cbb3d77c6c7eebd29 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 14 Jul 2017 14:43:00 +1200 Subject: [PATCH] Create guide for namespacing user-code Rewrite of outdated api references Improve documentation for logging --- docs/en/04_Changelogs/4.0.0.md | 500 +++++++++++++++++++-------------- 1 file changed, 294 insertions(+), 206 deletions(-) diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index e05547179..f1b29d5b6 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -10,6 +10,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0 * [Upgrading Guide](#upgrading) * [Standard Upgrade](#upgrading-primary) * [API Specific Upgrades](#upgrading-specifics) + * [User code style upgrades](#usercode-style-upgrades) * [API Changes](#api-changes) * [General and Core API](#overview-general) * [ORM API](#overview-orm) @@ -18,7 +19,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0 * [i18n](#overview-i18n) * [Email and Mailer](#overview-mailer) * [SapphireTest](#overview-testing) -* [Commit History](#commit-history) + * [Security](#overview-security) ## Highlights of major changes @@ -26,10 +27,13 @@ guide developers in preparing existing 3.x code for compatibility with 4.0 is required. * All code earlier marked as deprecated for 4.0 has now been removed. See [deprecation documentation](/contributing/release_process) for information on code deprecation. -* All SilverStripe classes are now namespaced, and some have been renamed. This has major implications for +* All code has been migrated to follow the PSR-2 coding standard. most significantly, all SilverStripe + classes are now namespaced, and some have been renamed. This has major implications for arrangement of templates, as well as other references to classes via string literals or configuration. Automatic upgrading tools have been developed to cope with the bulk of these changes (see - [upgrading notes](#upgrading)). + [upgrading notes](#upgrading)). Each class has been arrange in a single file per class, and it is recommended + that user code follows the same convention. + For example, page types and their controllers should longer be collated in a single file. * Object class has been removed. * Asset storage has been abstracted, and a new concept of `DBFile` references via database column references now exists in addition to references via the existing `File` dataobject. File security and protected files @@ -59,7 +63,6 @@ guide developers in preparing existing 3.x code for compatibility with 4.0 [behat extension](https://github.com/silverstripe/silverstripe-behat-extension) for more information. * The `GDBackend` and `ImagickBackend` classes have been replaced by a unified `InterventionBackend` which uses the [intervention/image](https://github.com/intervention/image) library to power manipualations. -* Page types and their controllers can should longer be collated in a single file ## Upgrading @@ -75,7 +78,14 @@ as a standard first point of upgrade. #### Upgrade references to renamed and namespaced classes Nearly all core PHP classes have been namespaced. For example, `DataObject` is now called `SilverStripe\ORM\DataObject`. -We have developed an [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) to (semi-)automatically update your 3.x code to the new naming. Here's an example how to upgrade your `mysite` folder: +The below tasks describe how to upgrade an existing site to remain compatible with the newly upgraded classes. + +##### Using the upgrader tool to automatically upgrade + +This task should be run on every upgraded project. + +We have developed an [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) to (semi-)automatically +update your 3.x code to the new naming. Here's an example how to upgrade your `mysite` folder: ``` composer global require silverstripe/upgrader @@ -93,125 +103,70 @@ For a full list of renamed classes, check the `.upgrade.yml` definitions in each The rename won't affect class-based permission codes or database table names. -##### Namespaced table names +##### Upgrade references to literal or class table names -Keep in mind that table names are namespaced too. To ease data migrations, use the `$table_name` config property of your `DataObject` subclasses. -This allows you to customise the table name to something simpler, or perhaps identical to its 3.x name. -```php -namespace SilverStripe\BannerManager; +In 3.x the class name of any dataobject matched the table name, but in 4.x all classes are namespaced, and it is +necessary to map between table and class for querying the database. -use SilverStripe\ORM\DataObject; +In order to ensure you are using the correct table for any class a new +[DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema) service is available to manage these mappings +(see [versioned documentation](/developer_guides/model/data_model_and_orm)). -class BannerImage extends DataObject -{ - private static $table_name = 'BannerImage'; -} -``` +For example, the below shows how you would update a query with a hard-coded table name: -In order to ensure you are using the correct table for any class a new [DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema) service -is available to manage these mappings (see [versioned documentation](/developer_guides/model/data_model_and_orm)). - - -```php +```diff public function countDuplicates($model, $fieldToCheck) { - $table = SilverStripe\ORM\DataObject::getSchema()->tableForField($model, $field); $query = new SilverStripe\ORM\Queries\SQLSelect(); - $query->setFrom("\"{$table}\""); - $query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]); - return $query->count(); ++ $table = SilverStripe\ORM\DataObject::getSchema()->tableForField($model, $field); +- $query->setFrom("\"{$model}\""); ++ $query->setFrom("\"{$table}\""); +- $query->setWhere(["\"{$model}\".\"{$field}\"" => $model->$fieldToCheck]); ++ $query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]); + return $query->count(); } ``` -##### Class name remapping - -If you've namespaced one of your custom page types, you may notice a message in the CMS - telling you it's obsolete. This is likely because the `ClassName` -field in the `SiteTree` table still contains the singular model name, e.g. `BlogPost` -and that when you change it to `SilverStripe\Blog\Model\BlogPost` then everything -works again. - -The `dev/build` task is configured to look for a legacy class name mapping -configuration setting and will update this for you automatically. For an example, -refer to `_config/legacy.yml` in the CMS module. The Blog module, for instance, uses -this: - -```yaml -SilverStripe\ORM\DatabaseAdmin: - classname_value_remapping: - Blog: SilverStripe\Blog\Model\Blog - BlogCategory: SilverStripe\Blog\Model\BlogCategory - BlogPost: SilverStripe\Blog\Model\BlogPost - BlogTag: SilverStripe\Blog\Model\BlogTag -``` - -##### Other namespacing gotchas +##### Replacing literal strings with fully qualified class name, or ::class constant You'll need to update any strings that represent class names and make sure they're fully qualified. In particular, relationship definitions such as `has_one` and `has_many` will need to be updated to refer to fully qualified class names. -```ss - -private static $has_one = [ - 'MyRelation' => 'MyObject', -]; - -private static $has_one = [ - 'MyRelation' => MyObject::class, -]; +In configs and with literal php strings it is recommended to use the php `::class` constant, +as demonstrated below. + +```diff + 'Member', + + 'Author' => Member::class, + ]; +} ``` In the context of YAML, the magic constant `::class` does not apply. Class names must be hard coded. -```ss - -MyObject: - property: value - -My\Project\MyObject: +```diff +-MyObject: ++My\Project\MyObject: property: value ``` -When dealing with custom database queries, you need to be judicious about replacing table -names expressed literally. The upgrader will automatically replace anything that looks -like an unqualified class name with a fully qualified one, but a fully qualified -class name may not be the same as the table name, as explained above, in the case of -`$table_name`. +##### Update references to literal classes to use Injector -For example: -```php -->innerJoin('BlogPost', sprintf('"BlogPost%s"."ID" = "SiteTree%s"."ID"', $stage, $stage)) -``` -The upgrader will replace `BlogPost` with `SilverStripe\\Blog\\Model\BlogPost` as the fully qualified class name. In the case of innerJoin(), it asks for a table name which have remained the same as SilverStripe 3.x, so this should not be namespaced. This of course depends on what you’ve configured your table name to be. +In many places certain classes namespaced in SilverStripe 4.x will still have a non-namespaced service +name reference that can be referred to with injector. You should upgrade direct object constructors with +Injector api. -A better option is to replace it with `DataObject::getSchema()->tableName(BlogPost::class)` as long as the class has already been imported into your current namespace. +E.g. -##### Namespacing tip: use ::class whenever possible - -Prefer: - ```php - private static $has_one = [ - 'MyObject' => MyObject::class - ]; - ``` - to - - ```php -private static $has_one = [ - 'MyObject' => 'My\Project\MyObject' -]; -``` - -##### Namespacing tip: use Injector whenever possible - -This can help with reducing changes from 3.x to 4.x by allowing your code to -continue to use `MyClassName`, while you tell the injector what the FQ class name is: - -```yaml -SilverStripe\Core\Injector\Injector: - MyClassName: - class: Me\MyModule\MyClassName # fully qualified! +```diff +- $field = new Varchar(); ++ $field = Injector::inst()->create('Varchar'); ``` #### Migrate your controllers to their own files @@ -241,7 +196,7 @@ Core template locations have moved - if you're including or overriding these no longer exists, and instead template locations will be placed in paths that match the `SilverStripe\Forms` namespace. -#### Upgrade your member statics +#### Upgrade static config settings to `private static` If you have some class configuration statics defined and they aren't private, you may find that they don't register anymore. For example, this code, taken from @@ -266,82 +221,6 @@ private static $allowed_actions = [ ]; ``` -#### Get on board with PSR-4 autoloading - -While not critical to an upgrade, SilverStripe 4.0 has adopted the [PS-4 autoloading](http://www.php-fig.org/psr/psr-4/) -standard for the core modules, so it's probably a good idea to be consistent. - -You can implement this in your composer configuration like so: - -```js - ... - "autoload": { - "psr-4": { - "SilverStripe\\Blog\\": "src/" - } - }, - ... -``` -Now you just need to ensure that each class site in the correct folder location -(including case sensitivity) to match its namespace. For example, - `SilverStripe\Blog\Model\BlogPost` should live at `src/Model/BlogPost.php`. - -Note that you don’t have to use `src/` as the root folder name. You can continue -to use `code/` if you want to. SilverStripe has adopted the PSR-4 approach and -have also started to use `src/` as a default folder location instead of -`code/`. If you’re going to change, it's probably a good time to do it while you're -upgrading. - -For examples, take a look at the file/folder structure and to the -`composer.json` configuration in either the `framework` or `cms` modules. - -Please note that there are changes to template structure which in some cases -require templates to be in a folder location that matches the namespace of the class -that it belongs to, e.g. `themes/mytheme/templates/MyVendor/Foobar/Model/MyModel.ss`. - -#### Get on board with PSR-# logging, too - -One of the great changes that comes with SilverStripe 4 is the introduction of -[PSR-3](http://www.php-fig.org/psr/psr-3/) compatible logger interfaces. This -means we can use thirdparty services like Monolog. - -Note that the old `SS_Log` class has been renamed to `SilverStripe\Logging\Log`, -but has also had most of its logic stripped out and the remainder is marked as -deprecated. - -```ss - -SS_Log::log('My error message', SS_Log::ERR); - -Injector::inst()->get('LoggerInterface::class')->error('My error message'); -``` - -If you call that multiple times in one class, you might want to add a getter -method to centralize it a little - but you probably should've had that already -in this case! - -##### Unit tests and PSR-3 loggers - -If you have some code that you've tested originally using `SS_Log`, and now moved -to using a Monolog Logger then you may notice occasions where your unit tests pass -but output a bunch of warnings, debug information, etc. to `stderr`. This is because - Monolog will use `stderr` as the default Handler unless you specify one. - -This isn't so much a regression as simply that you're now seeing things happening -in your code that didn't used to happen. You have a couple of options here. You can either: - -* Address that there's possibly a logic problem in your unit tests -* Push a conveniently named `NullHandler` to monolog to quiet it. - -```php -$logger = Injector::inst()->get('Logger'); -$logger->pushHandler(new \Monolog\Handler\NullHandler); -``` - -It is not recommended to do this in your actual class. It's better to make use of -a convenient public getter method and configure this from the `setUp()` method of -your unit test class. - #### Upgrade module paths in file references You should no longer rely on modules being placed in a deterministic folder (e.g. `/framework`), @@ -405,28 +284,90 @@ To ensure consistency, we've also deprecated support for path constants: The below sections deal with upgrades to specific parts of various API. Projects which rely on certain API should be upgraded as appropriate using any of the relevant processes documented below. +#### Upgrade references of SS_Log to use PSR-3 logging + +One of the great changes that comes with SilverStripe 4 is the introduction of +[PSR-3](http://www.php-fig.org/psr/psr-3/) compatible logger interfaces. This +means we can use thirdparty services like Monolog. `SS_Log` has been replaced +with a logger which can be accessed using the LoggerInterface::class service. + +For instance, code which logs errors should be upgraded as below: + +```diff +-SS_Log::log('My error message', SS_Log::ERR); ++use Psr\Log\LoggerInterface; ++Injector::inst()->get(LoggerInterface::class)->error('My error message'); +``` + +##### How to customise the default PSR-3 logger + +If necessary, you may need to customise either the default logging handler, or +one of the error formatters. For example, if running unit tests you may want to +suppress errors. You can temporarily disable logging by setting a `NullHandler` + +```yml +--- +Name: custom-dev-logging +After: dev-logging +Only: + environment: dev +--- +# Replace default handler with null +SilverStripe\Core\Injector\Injector: + Monolog\Handler\HandlerInterface: Monolog\Handler\NullHandler +``` + +Alternatively you can customise one or both of the below services: + - `Monolog\Formatter\FormatterInterface.detailed` service, which is used for displaying + detailed error messages useful for developers. + - `Monolog\Formatter\FormatterInterface.friendly` service, which is used to display "error" + page content to visitors of the site who encounter errors. + +For example, a custom error page generator could be added as below: + +```yml +--- +Name: custom-errorpage +After: + - '#loggingformatters' +--- +SilverStripe\Core\Injector\Injector: + Monolog\Formatter\FormatterInterface.friendly: + class: WebDesignGroup\ShopSite\Logging\ErrorPageFormatter +``` + +`WebDesignGroup\ShopSite\Logging\ErrorPageFormatter` should be a class that +implements the `Monolog\Formatter\FormatterInterface` interface. + #### Upgrade `mysite/_config.php` The globals `$database` and `$databaseConfig` are deprecated. You should upgrade your site _config.php files to use the `.env` configuration (below). -If you need to configure database details in PHP, use the new `DB::setConfig()` api instead. +`conf/ConfigureFromEnv.php` is also no longer used, and references to this file should be deleted. + +If you need to configure database details in PHP you should configure these details via `.env` file, +or alternatively (but less recommended) use the new `DB::setConfig()` api. The global `$project` is deprecated in favour of the configuration setting `SilverStripe\Core\Manifest\ModuleManifest.project`. -```ss - -global $project; -$project = 'mysite'; - +Changes to `mysite/_config.php`: + +```diff + 'Varchar(200)', 'Suburb' => 'Varchar(100)', 'City' => 'Varchar(100)', 'Country' => 'Varchar(100)' ]; - public function scaffoldFormField($title = null) + public function scaffoldFormField($title = null, $params = null) { new SilverStripe\Forms\TextField($this->getName(), $title); } @@ -1457,7 +1398,7 @@ specific functions. The methods `register` and `unregister` on `Authenticator` are deprecated in favor of the `Config` system. This means that any custom Authenticator needs to be registered through the yml config: -```yaml +```yml SilverStripe\Security\Authenticator; authenticators: - MyVendor\MyModule\MyAuthenticator @@ -1465,7 +1406,7 @@ SilverStripe\Security\Authenticator; If there is no authenticator registered, `Authenticator` will try to fall back on the `default_authenticator`, which can be changed using the following config, replacing the MemberAuthenticator with your authenticator: -```yaml +```yml SilverStripe\Security\Authenticator: default_authenticator: SilverStripe\Security\MemberAuthenticator ``` @@ -1501,7 +1442,6 @@ This also allowed us to remove SilverStripe's `Cache` API and use dependency inj Caches should be retrieved through `Injector` instead of `Cache::factory()`, and have a slightly different API (e.g. `set()` instead of `save()`). - ```diff -$cache = Cache::factory('myCache'); +use Psr\SimpleCache\CacheInterface; @@ -1528,10 +1468,9 @@ and have a slightly different API (e.g. `set()` instead of `save()`). +$cache->delete('myCacheKey'); ``` -With the necessary minimal config in `_config/mycache.yml` +With the necessary minimal config in _config/mycache.yml - -```yaml +```yml --- Name: mycache --- @@ -1542,13 +1481,12 @@ SilverStripe\Core\Injector\Injector: namespace: 'mycache' ``` - ##### Configuration Changes Caches are now configured through dependency injection services instead of PHP. See our ["Caching" docs](/developer-guides/performance/caching) for more details. -Before (`mysite/_config.php`): +Before (mysite/_config.php): ```php Cache::add_backend( @@ -1564,9 +1502,9 @@ Cache::add_backend( Cache::pick_backend('primary_memcached', 'any', 10); ``` -After (`mysite/_config/config.yml`): +After (mysite/_config/config.yml): -```yaml +```yml --- After: - '#corecache' @@ -1582,6 +1520,158 @@ SilverStripe\Core\Injector\Injector: client: '%$MemcachedClient ``` +### User-code style upgrades + +Although it is not mandatory to upgrade project code to follow SilverStripe and +PSR-2 standard it is highly recommended to ensure that code is consistent. The below sections +will assist you with bringing yourself up to speed. + +Please note that before upgrading user code style it is necessary to run the standard upgrade path +to fix references and usages of framework API. + +#### Upgrading user-code to use namespaces + +Upgrading code to use namespaces is quite a complex process, and as such we have provided +several development tools and processes to help make the upgrade user friendly and as +automated as possible. + +##### Using the upgrader tool to automatically apply namespaces + +HThe [upgrader tool](https://github.com/silverstripe/silverstripe-upgrader/) provides a feature +to not only automatically namespace code, but will provide automatic upgrade of other code +references to those classes. + +Use the below to setup upgrader, and apply a namespace to a given code folder. + +``` +composer global require silverstripe/upgrader +cd ~/Project/Root +~/.composer/vendor/bin/upgrade-code add-namespace "WebDesignGroup\ShopSite" ./mysite/code --recursive --write +``` + +If you want to do a dry-run, omit the `--write` option to see a preview of a diff of +all changed project files. + +This task will do the following: + + - Add the given namespace to all files in the code class, and subdirectories. + - Sub-namespaces will be applied based on directory structure + - All references to classes in any namespaced files will be safely retained with additional `use` directives + added as necessary. + - Register all namespaced classese in a mysite/.upgrade.yml file for migration of other code + +This task will not do the following, and must be done manually: + + - Adding `table_name` to any namespaced classes + - Upgrade other references to namespaced classes outside of this folder + - Migrate any database table records + +Please see the following steps for more information. + +##### Using the upgrader tool to update references to namespaced user classes + +Once a project has been namespaced all newly renamed classes will have a mapping included in the `mysite/.upgrade.yml` +file. If you have any user-code that references these, you may need to run the upgrader again (as you did did +to upgrade your project to namespaced framework classe). + +``` +composer global require silverstripe/upgrader +cd ~/Project/Root +~/.composer/vendor/bin/upgrade-code upgrade ./othercode --write +``` + +##### Updating custom dataobjects to use existing table names + +Once you have namespaced your user code it will be necessary to customise the `table_name` config +for your dataobjects, in order to ensure the correct table is used after upgrade. It is recommended +to point this to the base name of the class, excluding namespace, as in 3.x. + +```diff +namespace WebDesignGroup\ShopSite; +use SilverStripe\ORM\DataObject; +use Page; +class GalleryPage extends Page +{ ++ private static $table_name = 'GalleryPage'; +} +``` + +##### Class name remapping + +If you've namespaced one of your custom page types, you may notice a message in the CMS +telling you it's obsolete. This is likely because the `ClassName` +field in the `SiteTree` table still contains the singular model name, e.g. `GalleryPage` +and that when you change it to `WebDesignGroup\ShopSite\GalleryPage` then everything +works again. + +The `dev/build` task is configured to look for a legacy class name mapping +configuration setting and will update this for you automatically. You can use +this to add DB upgrading rules for your own classes. + +For example, you could upgrade references to the newly namespaced Banner class by adding +this to your `mysite/_config/upgrade.yml` file: + +```yml +SilverStripe\ORM\DatabaseAdmin: + classname_value_remapping: + GalleryPage: WebDesignGroup\ShopSite\GalleryPage +``` + +The next time you run a dev/build the class name for all `GalleryPage` pages will +be automatically updated to the new `WebDesignGroup\ShopSite\GalleryPage` + +#### Using php code checker to automatically update to PSR-2 + +You can use the [php codesniffer](https://github.com/squizlabs/PHP_CodeSniffer) tool +to not only detect and lint PSR-2 coding errors, but also do some minimal automatic +code style migration. + + - Install the necessary library: + `composer require squizlabs/php_codesniffer` + - Copy silverstripe standards config file from framework/phpcs.xml to your project root: + `cp ./framework/phpcs.xml ./phpcs.xml` + - Run the automatic upgrade tool on your code folder + `vendor/bin/phpcbf ./mysite/code` + - Run the automatic linting tool to detect and manually fix other errors: + `vendor/bin/phpcs ./mysite/code` + +Repeat the final step and manually repair suggested changes, as necessary, +until you no longer have any linting issues. + +#### Upgrade user-code to use PSR-4 autoloading + +While not critical to an upgrade, SilverStripe 4.0 has adopted the [PS-4 autoloading](http://www.php-fig.org/psr/psr-4/) +standard for the core modules, so it's probably a good idea to be consistent. + +You can implement this in your composer configuration like so: + +```json +{ + "autoload": { + "psr-4": { + "WebDesignGroup\\ShopSite\\": "mysite/src/" + } + } +} +``` + +Now you just need to ensure that each class site in the correct folder location +(including case sensitivity) to match its namespace. For example, + `WebDesignGroup\ShopSite\Model\GalleryItem.php` should live at `mysite/src/Model/GalleryItem.php`. + +Note that you don’t have to use `src/` as the root folder name. You can continue +to use `code/` if you want to. SilverStripe has adopted the PSR-4 approach and +have also started to use `src/` as a default folder location instead of +`code/`. If you’re going to change, it's probably a good time to do it while you're +upgrading. + +For examples, take a look at the file/folder structure and to the +`composer.json` configuration in either the `framework` or `cms` modules. + +Please note that there are changes to template structure which in some cases +require templates to be in a folder location that matches the namespace of the class +that it belongs to, e.g. `themes/mytheme/templates/MyVendor/Foobar/Model/MyModel.ss`. + ## API Changes ### General and Core API @@ -2303,10 +2393,8 @@ New `TimeField` methods replace `getConfig()` / `setConfig()` * `i18n::get_language_code()` removed. * `i18n::get_common_locales()` removed. * `i18n.common_locales` config removed - -### Email and Mailer -#### Email Additions / Changes +#### Email Additions / Changes * `Mailer` converted to an interface * `SwfitMailer` added as new default mailer @@ -2327,7 +2415,7 @@ New `TimeField` methods replace `getConfig()` / `setConfig()` * `SapphireTest::$extraDataObjects` renamed to `SapphireTest::$extra_dataobjects` and made static * `SapphireTest::$extraControllers` renamed to `SapphireTest::$extra_controllers` and made static -### Security +### Security * `LoginForm` now has an abstract method `getAuthenticatorName()`. If you have made subclasses of this, you will need to define this method and return a short name describing the login method.