Skip to content

Commit

Permalink
Merge branch '5.1' into 5
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Feb 1, 2024
2 parents 1a6f473 + b09e887 commit b8fdfcb
Show file tree
Hide file tree
Showing 115 changed files with 1,872 additions and 2,355 deletions.
70 changes: 32 additions & 38 deletions en/00_Getting_Started/02_Composer.md
Expand Up @@ -22,9 +22,8 @@ installed globally. You should now be able to run the command:
composer help
```

[info]
If you already have Composer installed, make sure it is composer 2 by running `composer --version`. If you're running Composer 1, run [`composer self-update`](https://getcomposer.org/doc/03-cli.md#self-update-selfupdate). You may also want to check out the [upgrade guide for Composer 1.x to 2.0](https://getcomposer.org/upgrade/UPGRADE-2.0.md).
[/info]
> [!NOTE]
> If you already have Composer installed, make sure it is composer 2 by running `composer --version`. If you're running Composer 1, run [`composer self-update`](https://getcomposer.org/doc/03-cli.md#self-update-selfupdate). You may also want to check out the [upgrade guide for Composer 1.x to 2.0](https://getcomposer.org/upgrade/UPGRADE-2.0.md).
## Create a new site

Expand All @@ -35,14 +34,13 @@ version:
composer create-project silverstripe/installer my-project
```

[hint]
With the above command, `my-project` is the path (relative to your current working directory) where Composer will create the project.

For example, on OSX, you might want to create a project as a sub-directory of `~/Sites`. You could do that by running `cd ~/Sites` before
the above command.

If that directory doesn't exist, Composer will create it for you.
[/hint]
> [!TIP]
> With the above command, `my-project` is the path (relative to your current working directory) where Composer will create the project.
>
> For example, on OSX, you might want to create a project as a sub-directory of `~/Sites`. You could do that by running `cd ~/Sites` before
> the above command.
>
> If that directory doesn't exist, Composer will create it for you.
If you want to get additional fixtures for testing, such as behat and phpunit configuration, an
example `.env.example` file, and all documentation, then it's recommended to use the `--prefer-source` option
Expand Down Expand Up @@ -87,11 +85,10 @@ a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-
composer require silverstripe/blog ^2
```

[warning]
**Version constraints:** `master` or `main` is not a legal version string - it's a branch name. These are different things. The
version string that would get you the branch is `dev-main`. The version string that would get you a numeric branch is
a little different. The version string for the `5` branch is `5.x-dev`.
[/warning]
> [!WARNING]
> **Version constraints:** `master` or `main` is not a legal version string - it's a branch name. These are different things. The
> version string that would get you the branch is `dev-main`. The version string that would get you a numeric branch is
> a little different. The version string for the `5` branch is `5.x-dev`.
## Updating dependencies

Expand All @@ -107,14 +104,13 @@ composer update
Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific
commits and version constraints for each of them.

[hint]
The update command can also be used to *downgrade* dependencies - if you edit your `composer.json` file and set a version
constraint that will require a lower version to be installed, running `composer update` will "update" your installed
dependencies to match your constraints, which in this case would install lower versions than what you had previously.

You may occasionally need to use the `--with-all-dependencies` option when downgrading dependencies to avoid conflicting
version constraints.
[/hint]
> [!TIP]
> The update command can also be used to *downgrade* dependencies - if you edit your `composer.json` file and set a version
> constraint that will require a lower version to be installed, running `composer update` will "update" your installed
> dependencies to match your constraints, which in this case would install lower versions than what you had previously.
>
> You may occasionally need to use the `--with-all-dependencies` option when downgrading dependencies to avoid conflicting
> version constraints.
## Deploying projects with Composer

Expand Down Expand Up @@ -229,10 +225,9 @@ file. It will appear in your project root, and by default, it will look somethin
To add modules, you should add more entries into the `"require"` section. For example, we might add the blog and forum
modules.

[notice]
Be careful with the commas at the end of the lines! You can run `composer validate` to be sure your `composer.json`
file is formatted correctly.
[/notice]
> [!WARNING]
> Be careful with the commas at the end of the lines! You can run `composer validate` to be sure your `composer.json`
> file is formatted correctly.
Save your file, and then run the following command to update the installed packages:

Expand Down Expand Up @@ -303,16 +298,15 @@ This is how you do it:
composer require silverstripe/cms
```

[notice]
In most cases, you will probably have a specific branch of the fork you want to install. You should use the appropriate
version constraint to install that branch. For example, to install a branch called `fix/issue-1990` your version constraint
should be `dev-fix/issue-1990`.

Depending on what other installed modules have that package as a dependency, you may also need to declare an
[inline alias](https://getcomposer.org/doc/articles/aliases.md#require-inline-alias).

See more about this in [Forks and branch names](#forks-and-branch-names) below.
[/notice]
> [!WARNING]
> In most cases, you will probably have a specific branch of the fork you want to install. You should use the appropriate
> version constraint to install that branch. For example, to install a branch called `fix/issue-1990` your version constraint
> should be `dev-fix/issue-1990`.
>
> Depending on what other installed modules have that package as a dependency, you may also need to declare an
> [inline alias](https://getcomposer.org/doc/articles/aliases.md#require-inline-alias).
>
> See more about this in [Forks and branch names](#forks-and-branch-names) below.
Composer will scan all of the repositories you list, collect meta-data about the packages within them, and use them in
favour of the packages listed on packagist. To switch back to using the mainline version of the package, just remove
Expand Down
19 changes: 8 additions & 11 deletions en/00_Getting_Started/03_Environment_Management.md
Expand Up @@ -54,11 +54,10 @@ use SilverStripe\Core\Environment;
Environment::getEnv('SS_DATABASE_CLASS');
```

[hint]
The `Environment::getEnv()` method will return `false` both if there was no value set for a variable or if
the variable was explicitly set as `false`. You can determine whether a variable has been set by calling
[`Environment::hasEnv()`](api:SilverStripe\Core\Environment::hasEnv()).
[/hint]
> [!TIP]
> The `Environment::getEnv()` method will return `false` both if there was no value set for a variable or if
> the variable was explicitly set as `false`. You can determine whether a variable has been set by calling
> [`Environment::hasEnv()`](api:SilverStripe\Core\Environment::hasEnv()).
Individual settings can be assigned via [`Environment::setEnv()`](api:SilverStripe\Core\Environment::setEnv()) or [`Environment::putEnv()`](api:SilverStripe\Core\Environment::putEnv()) methods.

Expand All @@ -67,9 +66,8 @@ use SilverStripe\Core\Environment;
Environment::setEnv('API_KEY', 'AABBCCDDEEFF012345');
```

[warning]
`Environment::getEnv()` will return `false` whether the variable was explicitly set as `false` or simply wasn't set at all. You can use [`Environment::hasEnv()`](api:SilverStripe\Core\Environment::hasEnv()) to check whether an environment variable was set or not.
[/warning]
> [!WARNING]
> `Environment::getEnv()` will return `false` whether the variable was explicitly set as `false` or simply wasn't set at all. You can use [`Environment::hasEnv()`](api:SilverStripe\Core\Environment::hasEnv()) to check whether an environment variable was set or not.
### Using environment variables in config

Expand All @@ -91,9 +89,8 @@ $loader = new EnvironmentLoader();
$loader->loadFile($env);
```

[warning]
Note that because `_config.php` is processed after YAML configuration, variables set in these extra `.env` files cannot be used inside YAML config.
[/warning]
> [!WARNING]
> Note that because `_config.php` is processed after YAML configuration, variables set in these extra `.env` files cannot be used inside YAML config.
## Core environment variables

Expand Down
5 changes: 2 additions & 3 deletions en/00_Getting_Started/index.md
Expand Up @@ -29,9 +29,8 @@ Within the newly created `my-project` folder, point your webserver at the `publi

Now create a `.env` file in your project root (not the `public/` folder).

[hint]
If you used `silverstripe/installer` to create your project, you can rename the `.env.example` file to `.env`. It includes the minimum required [environment variables](environment_management).
[/hint]
> [!TIP]
> If you used `silverstripe/installer` to create your project, you can rename the `.env.example` file to `.env`. It includes the minimum required [environment variables](environment_management).
Replace the placeholders as required:

Expand Down
83 changes: 35 additions & 48 deletions en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md
Expand Up @@ -43,21 +43,19 @@ class Player extends DataObject
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
so on. After writing this class, we need to regenerate the database schema.

[hint]
You can technically omit the `table_name` property, and a default table name will be created based on the fully qualified class name - but this can result in table names that are too long for the database engine to handle. We recommend that you *always* explicitly declare a table name for your models.

See more in [Mapping classes to tables with `DataObjectSchema`](#mapping-classes-to-tables) below.
[/hint]
> [!TIP]
> You can technically omit the `table_name` property, and a default table name will be created based on the fully qualified class name - but this can result in table names that are too long for the database engine to handle. We recommend that you *always* explicitly declare a table name for your models.
>
> See more in [Mapping classes to tables with `DataObjectSchema`](#mapping-classes-to-tables) below.
## Generating the database schema

After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your Silverstripe CMS database. The
database schema is generated automatically by visiting `/dev/build` (e.g. `https://www.example.com/dev/build`) in your browser
while authenticated as an administrator, or by running `sake dev/build` on the command line (see [Command Line Interface](/developer_guides/cli/) to learn more about `sake`).

[info]
In "dev" mode, you do not need to be authenticated to run `/dev/build`. See [Environment Types](/developer_guides/debugging/environment_types) for more information.
[/info]
> [!NOTE]
> In "dev" mode, you do not need to be authenticated to run `/dev/build`. See [Environment Types](/developer_guides/debugging/environment_types) for more information.
This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema
as required.
Expand Down Expand Up @@ -119,11 +117,10 @@ However, a better way is to use the `create()` method.
$player = Player::create();
```

[hint]
Using the `create()` method provides chainability (known as a "fluent API" or "[fluent interface](https://en.wikipedia.org/wiki/Fluent_interface)"), which can add elegance and brevity to your code, e.g. `Player::create(['FirstName' => 'Sam'])->write()`.

More importantly, however, it will look up the class in the [`Injector`](api:SilverStripe\Core\Injector\Injector) so that the class can be overridden by [dependency injection](../extending/injector). For this reason, instantiating records using the `new` keyword is considered bad practice.
[/hint]
> [!TIP]
> Using the `create()` method provides chainability (known as a "fluent API" or "[fluent interface](https://en.wikipedia.org/wiki/Fluent_interface)"), which can add elegance and brevity to your code, e.g. `Player::create(['FirstName' => 'Sam'])->write()`.
>
> More importantly, however, it will look up the class in the [`Injector`](api:SilverStripe\Core\Injector\Injector) so that the class can be overridden by [dependency injection](../extending/injector). For this reason, instantiating records using the `new` keyword is considered bad practice.
Database columns (aka fields) can be set as class properties on the object. The Silverstripe CMS ORM handles the saving
of the values through a custom `__set()` method.
Expand Down Expand Up @@ -152,6 +149,9 @@ $id = $player->write();
With the `Player` class defined we can query our data using the ORM. The ORM provides
shortcuts and methods for fetching, sorting and filtering data from our database.

> [!TIP]
> All of the below methods to get a single record will return `null` if there is no record to return.
```php
// returns a `DataList` containing all the `Player` objects.
$players = Player::get();
Expand All @@ -165,13 +165,8 @@ $player = Player::get()->byID(2);
$player = Player::get_by_id(2);
```

[hint]
All of the above methods to get a single record will return `null` if there is no record to return.
[/hint]

[info]
`DataObject::get()->byID()` and `DataObject::get_by_id()` achieve similar results, though the object returned by `DataObject::get_by_id()` is cached against a `static` property within `DataObject`.
[/info]
> [!NOTE]
> `DataObject::get()->byID()` and `DataObject::get_by_id()` achieve similar results, though the object returned by `DataObject::get_by_id()` is cached against a `static` property within `DataObject`.
The ORM uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
are `filter()` and `sort()`:
Expand All @@ -185,17 +180,15 @@ $members = Player::get()->filter([

There's a lot more to filtering and sorting, so make sure to keep reading.

[info]
Values passed in to the `filter()` and `sort()` methods are automatically escaped and do not require any additional escaping. This makes it easy to safely filter/sort records by user input.
[/info]
> [!NOTE]
> Values passed in to the `filter()` and `sort()` methods are automatically escaped and do not require any additional escaping. This makes it easy to safely filter/sort records by user input.
### Querying data when you have a record

When you have a `DataObject` record, it already has all of its database field data attached to it. You can get those values without triggering further database queries.

[notice]
This does not apply to relations, which are always lazy loaded. See the [Relations between Records](relations) documentation for more information.
[/notice]
> [!WARNING]
> This does not apply to relations, which are always lazy loaded. See the [Relations between Records](relations) documentation for more information.
```php
$player = Player::get()->byID(2);
Expand All @@ -215,9 +208,8 @@ All database fields have a default value format which you can retrieve by treati

The ORM doesn't actually execute the [SQLSelect](api:SilverStripe\ORM\Queries\SQLSelect) query until you iterate on the result (e.g. with a `foreach()` or `<% loop %>`).

[hint]
Some convenience methods (e.g. [`column()`](api:SilverStripe\ORM\DataList::column()) or aggregator methods like [`min()`](api:SilverStripe\ORM\DataList::min())) will also execute the query.
[/hint]
> [!TIP]
> Some convenience methods (e.g. [`column()`](api:SilverStripe\ORM\DataList::column()) or aggregator methods like [`min()`](api:SilverStripe\ORM\DataList::min())) will also execute the query.
It's smart enough to generate a single efficient query at the last moment in time without needing to post-process the
result set in PHP. In `MySQL` the query generated by the ORM may look something like this
Expand Down Expand Up @@ -267,9 +259,8 @@ if ($players->exists()) {
}
```

[hint]
While you could use `if ($players->Count() > 0)` for this condition, the `exists()` method uses an `EXISTS` SQL query, which is more performant.
[/hint]
> [!TIP]
> While you could use `if ($players->Count() > 0)` for this condition, the `exists()` method uses an `EXISTS` SQL query, which is more performant.
See the [Lists](lists) documentation for more information on dealing with [SS_List](api:SilverStripe\ORM\SS_List) instances.

Expand Down Expand Up @@ -454,20 +445,18 @@ $teams = Team::get()->filter('Players.Avg(PointsScored):GreaterThan', 15);
$teams = Team::get()->filter('Players.Sum(PointsScored):LessThan', 300);
```

[hint]
The above examples are using "dot notation" to get the aggregations of the `Players` relation on the `Teams` model. See [Relations between Records](relations) to learn more.
[/hint]
> [!TIP]
> The above examples are using "dot notation" to get the aggregations of the `Players` relation on the `Teams` model. See [Relations between Records](relations) to learn more.
### `filterByCallback`

It is possible to filter by a PHP callback using the [`filterByCallback()`](api:SilverStripe\ORM\DataList::filterByCallback()) method. This will force the data model to fetch all records and loop them in
PHP which will be much worse for performance, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`.

[notice]
Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.

`filterByCallback()` will always return an `ArrayList`.
[/notice]
> [!WARNING]
> Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.
>
> `filterByCallback()` will always return an `ArrayList`.
The first parameter to the callback is the record, the second parameter is the list itself. The callback will run once
for each record. The callback must return a boolean value. If the callback returns true, the current record will be included in the list of returned items.
Expand Down Expand Up @@ -757,11 +746,10 @@ $members = Member::get()
->innerJoin("Group_Members", '"Rel"."MemberID" = "Member"."ID"', "Rel");
```

[alert]
Using a join will *filter* results further by the JOINs performed against the foreign table. It will
**not return** the additionally joined data. For the examples above, we're still only selecting values for the fields
on the `Member` class table.
[/alert]
> [!CAUTION]
> Using a join will *filter* results further by the JOINs performed against the foreign table. It will
> **not return** the additionally joined data. For the examples above, we're still only selecting values for the fields
> on the `Member` class table.
### Default values

Expand Down Expand Up @@ -843,9 +831,8 @@ Product_Digital_Computer:
IsPreBuilt: 'Boolean'
```

[hint]
Note that because `DigitalProduct` doesn't define any new fields it doesn't need its own table. We should still declare a `$table_name` though - who knows if this model might have its own table created in the future (e.g. if we add fields to it later on).
[/hint]
> [!TIP]
> Note that because `DigitalProduct` doesn't define any new fields it doesn't need its own table. We should still declare a `$table_name` though - who knows if this model might have its own table created in the future (e.g. if we add fields to it later on).
Accessing the data is transparent to the developer.

Expand Down

0 comments on commit b8fdfcb

Please sign in to comment.