Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOC Document added generic types #438

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions en/04_Changelogs/5.2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ title: 5.2.0 (unreleased)
- [Buttons to select all files and deselect all files](#bulk-action-buttons)
- [New searchable dropdown fields](#searchable-dropdown-field)
- [More nuanced permissions for `/dev/*` routes](#dev-route-permissions)
- [Generic typehints](#generics)
- [New exception in React forms](#react-forms-exception)
- [Other new features](#other-new-features)
- [API changes](#api-changes)
Expand Down Expand Up @@ -194,6 +195,100 @@ Now, all of the controllers which handle these routes that come packaged in a co

You can also now optionally implement a `canView()` method on your `BuildTask` implementations to restrict accessed for specific tasks even further. This means you can grant access to *some* tasks to specific users or groups without granting access to *all* tasks.

### Generic typehints {#generics}

Typehints using PHPStan-style generic types have been added to PHPDocs in many areas of the codebase of supported modules. The primary goal of this is to improve the developer experience by correctly reporting to your IDE what types it should expect, for example when looping through a `DataList`. In many cases your IDE will now know what types to expect without needing you to prompt it with [`@var` annotation comments](https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/var.html).

[info]
There are some cases where this goal conflicts with having types that are correctly identified by PHPStan itself (or other static analysis tools). For example conditional return types aren't supported as widely in IDEs as generics themselves are, so we opted to not use conditional return types even when those would result in a more accurate type for static analysis tools.
[/info]

While you should see some improvements immediately after updating, there are some changes you can make to your own codebase to best use the new generic type hints.

See [Generics By Examples | PHPStan](https://phpstan.org/blog/generics-by-examples) and [Generics in PHP using PHP DocComments | DEVSENSE](https://blog.devsense.com/2022/generics-in-php-using-phpdoc) for more information about PHP generic typehints.

#### Generic typehints when returning lists {#generics-return-lists}

In your project code, any time you return an instance of `SS_List` (such as a `DataList` or `ArrayList`), you can add a generic typehint to declare what kind of object the returned list contains. This example will hint to the IDE that it returns a `DataList` containing `CarouselItem` records:

```php
use App\Model\CarouselItem;
use SilverStripe\ORM\DataList;

/**
* @return DataList<CarouselItem>
*/
function getCarouselItems(): DataList
{
return CarouselItem::get();
}
```

#### Generic typehints in `Extension` subclasses {#generics-extensions}

The generic typing on the `Extension` class can be used to tell your IDE what type to expect for the [`$owner`](api:SilverStripe\Core\Extension->owner) property and [`getOwner()`](api:SilverStripe\Core\Extension::getOwner()) method.

For this to be useful, you need to tell your IDE that your subclass `@extends` the `Extension` class, and tell it what type the owner should be.

[warning]
Don't forget to include a `use` statement, even if you're not explicitly referencing the type anywhere in your actual code. Your IDE needs the `use` statement to resolve the FQCN for the class you're referencing in the typehint.
[/warning]

```php
namespace App\Extension;

use SilverStripe\Core\Extension;
use SilverStripe\SiteConfig\SiteConfig;

/**
* @extends Extension<SiteConfig>
*/
class SiteConfigExtension extends Extension
{
// ...
}
```

This is also a useful way to indicate to developers at a glance what type(s) the extension is designed to be applied to.

For example you might have an extension that can apply to any `SiteTree` class, or to `LeftAndMain` and `GridFieldDetailForm_ItemRequest` classes, which you can indicate using a union typehint: `@extends Extension<LeftAndMain|GridFieldDetailForm_ItemRequest>`.

#### Generic typehints in `ContentController` subclasses {#generics-contentcontroller}

If you use the [`data()`](api:SilverStripe\CMS\Controllers\ContentController::data()) method or the [`$dataRecord`](api:SilverStripe\CMS\Controllers\ContentController->dataRecord) property in your page controllers, you may find it useful for your IDE to know specifically what page class that data represents.

For this to work, you need to make sure your base `PageController` class has a `@template` type to extend. Any time you use `@extends`, the class being extended needs to have a `@template` type so that your IDE knows what the type you're passing in is going to be used for.

```php
namespace {

use SilverStripe\CMS\Controllers\ContentController;

/**
* @template T of Page
* @extends ContentController<T>
*/
class PageController extends ContentController
{
// ...
}
}
```

```php
namespace App\PageType;

use PageController;

/**
* @extends PageController<HomePage>
*/
class HomepageController extends PageController
{
// ...
}
```

### New exception in react forms {#react-forms-exception}

A [`LogicException`](https://www.php.net/manual/en/class.logicexception.php) is now thrown by [`FormSchema::getSchema()`](api:SilverStripe\Forms\Schema::getSchema()) if a react component was not found for a field type.
Expand Down Expand Up @@ -229,6 +324,8 @@ The following unused API have been deprecated and will be removed in a future ma
- Public static method [`DataObject::disable_subclass_access()`](api:SilverStripe\ORM\DataObject::disable_subclass_access())
- Public static method [`DataObject::enable_subclass_access()`](api:SilverStripe\ORM\DataObject::enable_subclass_access())

The [`ViewableData::getIterator()`](api:SilverStripe\View\ViewableData::getIterator()) method has been deprecated and will be removed in a future major release.

### `silverstripe/cms` {#api-silverstripe-cms}

The [`SiteTree.hide_ancestor`](api:SilverStripe\CMS\Model\SiteTree->hide_ancestor) configuration property has been deprecated. Use [`SiteTree.hide_pagetypes`](api:SilverStripe\CMS\Model\SiteTree->hide_pagetypes) instead.
Expand Down
6 changes: 5 additions & 1 deletion phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@
<rule ref="SlevomatCodingStandard.Exceptions.DeadCatch" />
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly" />
<rule ref="SlevomatCodingStandard.Namespaces.UseFromSameNamespace" />
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses" />
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
<properties>
<property name="searchAnnotations" type="bool" value="true" />
</properties>
</rule>

<!-- Method bodies must at least have a comment - if no-op, use "// ..." -->
<rule ref="SlevomatCodingStandard.Functions.DisallowEmptyFunction" />
Expand Down