Create guide for namespacing user-code

Rewrite of outdated api references
Improve documentation for logging
This commit is contained in:
Damian Mooyman 2017-07-14 14:43:00 +12:00 committed by Daniel Hensby
parent 4f3d090020
commit 75a6a188e4
No known key found for this signature in database
GPG Key ID: B00D1E9767F0B06E

View File

@ -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)
## <a name="overview"></a>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
## <a name="upgrading"></a>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
<!-- before -->
private static $has_one = [
'MyRelation' => 'MyObject',
];
<!-- after -->
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
<?php
+ use SilverStripe\ORM\DataObject;
+ use SilverStripe\Security\Member;
class MyClass extends DataObject {
private static $has_one = [
- 'Author' => 'Member',
+ 'Author' => Member::class,
];
}
```
In the context of YAML, the magic constant `::class` does not apply. Class names must be hard coded.
```ss
<!-- before -->
MyObject:
property: value
<!-- after -->
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 youve 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 dont 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 youre 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
<!-- before -->
SS_Log::log('My error message', SS_Log::ERR);
<!-- after -->
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
<!-- before -->
global $project;
$project = 'mysite';
<!-- after -->
Changes to `mysite/_config.php`:
```diff
<?php
-global $project;
-$project = 'mysite';
-include 'conf/ConfigureFromEnv.php';
```
And also add to `mysite/_config/mysite.yml`:
```yml
SilverStripe\Core\Manifest\ModuleManifest:
project: mysite
```
Lastly, you should remove any references to `ConfigureFromEnv.php` in your project, as this file
is no longer necessary.
#### Upgrade of `_ss_environment.php` to `.env` configuration
The php configuration `_ss_environment.php` file has been replaced in favour of a non-executable
@ -449,7 +390,7 @@ $_FILE_TO_URL_MAPPING[__DIR__] = 'http://localhost';
// Database
define('SS_DATABASE_CHOOSE_NAME', true);
define('SS_DATABASE_CLASS', 'MySQLDatabase');
define('SS_DATABASE_USERNAME', 'root')
define('SS_DATABASE_USERNAME', 'root');
define('SS_DATABASE_PASSWORD', '');
define('SS_DATABASE_SERVER', '127.0.0.1');
```
@ -793,7 +734,7 @@ In YML format this will be expressed as the below:
`mymodule/lang/en.yml`:
```yaml
```yml
en:
MyObject:
SINGULAR_NAME: 'object'
@ -806,7 +747,7 @@ en:
`extendedmodule/lang/en.yml`:
```yaml
```yml
en:
AnotherSection:
DESCRIPTION: 'This is the description for this section'
@ -857,7 +798,7 @@ In order to retain existing file paths in line with framework version 3 you shou
Note that this will not allow you to utilise certain file versioning features in 4.0.
```yaml
```yml
SilverStripe\Filesystem\Flysystem\FlysystemAssetStore:
legacy_paths: true
```
@ -874,7 +815,7 @@ this task is run manually during an explicit migration process, as this process
large amounts of memory and run for an extended time.
```yaml
```yml
File:
migrate_legacy_file: true
```
@ -1095,14 +1036,14 @@ use SilverStripe\ORM\FieldType\DBComposite;
class MyAddressField extends DBComposite
{
private static $composite_db = ]
private static $composite_db = [
'Street' => '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
```
### <a name="usercode-style-upgrades"></a>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 dont 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 youre 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`.
## <a name="api-changes"></a>API Changes
### <a name="overview-general"></a>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
### <a name="overview-mailer"></a>Email and Mailer
#### <a name="overview-mailer-api"></a>Email Additions / Changes
#### <a name="overview-mailer"></a>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
### <a name="overview-testing"></a>Security
### <a name="overview-security"></a>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.