diff --git a/.editorconfig b/.editorconfig index dca0534cd0..b2621ff980 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + root = true [*] diff --git a/.flintci.yml b/.flintci.yml index 8258ef2e91..0de2252fa4 100644 --- a/.flintci.yml +++ b/.flintci.yml @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + services: composernormalize: true phpcsfixer: true diff --git a/.gitattributes b/.gitattributes index 667b29d794..14d001e6cf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + .* export-ignore *.md export-ignore tests export-ignore diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 208ccb2658..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,52 +0,0 @@ - - - - -### Environment - -#### Sonata packages - -``` -$ composer show --latest 'sonata-project/*' -# Put the result here. -``` - -#### Symfony packages - -``` -$ composer show --latest 'symfony/*' -# Put the result here. -``` - -#### PHP version - -``` -$ php -v -# Put the result here. -``` - -## Subject - - - -## Steps to reproduce - -## Expected results - -## Actual results - - diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md deleted file mode 100644 index e363f8e5cb..0000000000 --- a/.github/ISSUE_TEMPLATE/Question.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: ⛔ NO support questions -about: If you have a question, please check out our Slack or StackOverflow! ---- - -Hi, we try to keep Github issues for bug reports and feature requests only. -If you have a question, please ask it on Stack Overflow or on #sonata on [the symfony-devs slack](https://symfony.com/slack-invite). diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..7876e7c920 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + +blank_issues_enabled: false +contact_links: + - name: StackOverflow + url: https://stackoverflow.com/questions/tagged/sonata + about: 'Questions tagged with "sonata" on StackOverflow' + - name: Slack + url: https://symfony-devs.slack.com/archives/C3GC7MKM5 + about: '#sonata channel on Symfony Devs Slack' diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ee08883498..0e4647bc10 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + on: [push, pull_request] name: Lint diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index a796313469..0a7eb5136a 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -12,3 +12,11 @@ jobs: REQUIRE_DEV: true with: args: analyse + psalm: + name: Psalm + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Psalm + uses: docker://vimeo/psalm-github-actions diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index dd4acf6ea9..9679ec46c7 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + # https://github.com/actions/stale name: "Stale" diff --git a/.php_cs.dist b/.php_cs.dist index 47f50fea69..6f7ff02b54 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -1,6 +1,6 @@ + # Sonata project contribution Thanks for your interest in Sonata projects! @@ -266,15 +272,17 @@ The deprecated minor version **MUST NOT** be provided. Use `x` instead. It will Any deprecation **MUST** be documented in the corresponding `UPGRADE-[0-9].x.md`. The documentation **MUST** be filled inside the top **unreleased** section with a sub title. -The `NEXT_MAJOR` tag SHOULD not be used for deprecation. -The `@deprecated` and `E_USER_DEPRECATED` key will be searched for before releasing the next major version. +The `NEXT_MAJOR` tag SHOULD also be used for deprecations, it will be searched for before releasing the next major version. You have three ways to deprecate things. -For class definitions, methods (or first level functions) and properties, use the `@deprecated` tag: +For class definitions and properties, use the `@deprecated` tag. +For methods, use the `@deprecated` tag and trigger a deprecation with `@trigger_error('...', E_USER_DEPRECATED)`: ```php /** + * NEXT_MAJOR: Remove this class. + * * @deprecated since sonata-project/foo-lib 42.x, to be removed in 43.0. Use Shiny\New\ClassOfTheMonth instead. */ final class IAmOldAndUseless @@ -284,15 +292,24 @@ final class IAmOldAndUseless final class StillUsedClass { /** + * NEXT_MAJOR: Remove this property. + * * @deprecated since sonata-project/foo-lib 42.x, to be removed in 43.0. */ public $butNotThisProperty; /** + * NEXT_MAJOR: Remove this method. + * * @deprecated since sonata-project/foo-lib 42.x, to be removed in 43.0. */ public function iAmBatman() { + @trigger_error(sprintf( + 'Method %s() is deprecated since sonata-project/foo-lib 42.x and will be removed in version 43.0.', + __METHOD__ + ), E_USER_DEPRECATED); + echo "But this is not Gotham here."; } } @@ -301,6 +318,7 @@ final class StillUsedClass If the deprecated thing is a service, you **MUST** specify it on the service definition: ```xml + The "%service_id%" service is deprecated since sonata-project/bar-bundle 42.x and will be removed in 43.0. @@ -309,14 +327,15 @@ If the deprecated thing is a service, you **MUST** specify it on the service def More info: http://symfony.com/blog/new-in-symfony-2-8-deprecated-service-definitions -For everything else, not managed by the `@deprecated` tag, you **MUST** trigger a deprecation message. +For everything else, not managed by the `@deprecated` tag, +you **MUST** still trigger a deprecation message (and add a `NEXT_MAJOR` comment). ```php + # Sonata Admin Bundle The missing Symfony Admin Generator diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md deleted file mode 100644 index 206005b6a4..0000000000 --- a/UPGRADE-2.1.md +++ /dev/null @@ -1,13 +0,0 @@ -UPGRADE FROM 2.0 to 2.1 -======================= - -### Form - - * Due to refactoring in the Form Component, some type definitions have been changed and - new ones have been introduced: - - * sonata_type_model : this type now only renders a standard select widget or a list - widget (if the `multiple` option is set) - - * sonata_type_model_list : this type replaces the option `edit = list` provided as - a 4th argument on the `sonata_type_model` diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md deleted file mode 100644 index 493442834b..0000000000 --- a/UPGRADE-2.2.md +++ /dev/null @@ -1,10 +0,0 @@ -UPGRADE FROM 2.1 to 2.2 -======================= - -### Form - - * Refactoring the general layout to be more compact - * Remove the collapsing option to migrate to tabbed form layout - * Add tab menu to edit form - * Fix deprecated calls to the Form API - diff --git a/UPGRADE-2.3.md b/UPGRADE-2.3.md deleted file mode 100644 index 8497d87828..0000000000 --- a/UPGRADE-2.3.md +++ /dev/null @@ -1,19 +0,0 @@ -UPGRADE FROM 2.2 to 2.3 -======================= - -### Dependencies - -You'll need to follow the dependencies upgrade instructions. - -### Templates - - - `standard_layout.html.twig` has been updated (refactored layout accordingly to the theme). Some blocks have been moved, and the sonata_side_nav twig block became the main menu. - - Admin's sidemenus are now tab-menus; former tab-menus are now collapsible items. - -### Admin classes - - - `configureSideMenu` and `buildSideMenu` methods of the `Admin` class have been deprecated; they are replaced by `configureTabMenu` and `buildTabMenu`. - -### Remove configureShowField - - - `configureShowField` has been deprecated since a while now, use `configureShowFields` instead => add a `s`. \ No newline at end of file diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md deleted file mode 100644 index 696cf71765..0000000000 --- a/UPGRADE-3.0.md +++ /dev/null @@ -1,40 +0,0 @@ -UPGRADE FROM 2.x to 3.0 -======================= - -### Dependencies - -You will need to follow the dependencies upgrade instructions. - -## Deprecated BaseFieldDescription::camelize() and AdminHelper::camelize() - -If you were using this method, please use `Doctrine\Common\Inflector\Inflector::classify()` instead. - -## Datagrid builders - -If you have implemented a custom datagrid builder, you must adapt the signature of its `addFilter` method to match the one in `DatagridBuilderInterface` again. - -## sonata_type_model_autocomplete -CSS class `sonata-autocomplete-dropdown-item` is not automatically added to dropdown autocomplete item in `sonata_type_model_autocomplete`, use option `dropdown_item_css_class` to set the CSS class of dropdown item. - -## Standard Layout -`sonata_wrapper` block was moved and is now inside the `.wrapper` div of admin lte theme. - -## ErrorElement - -The inline validation has been migrating to CoreBundle. Just rename `Sonata\AdminBundle\Validator\ErrorElement` to `Sonata\CoreBundle\Validator\ErrorElement` - -## AdminLTE 2 - -AdminLTE version 2 has been integrated, this should work out of the box if you havn't change templates. If not you can review the upgrade guide here : [http://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#upgrade](http://almsaeedstudio.com/themes/AdminLTE/documentation/index.html#upgrade) - -## AclSecurityHandler - -In order to fix deprecated issue by spiting `SecurityContextInterface`, `AclSecurityHandler` constructor got a new argument. - -## AdminPoolLoader - -If you're using a custom implementation of `sonata.admin.route_loader` service, make sure to provide an array as 2nd argument since the type for this argument is now hinted to `array`. - -## LegacyModelsToArrayTransformer - -The `ModelsToArrayTransformer` has been renamed to `LegacyModelsToArrayTransformer`. `ModelsToArrayTransformer` should be only be used with SF2.7+ diff --git a/UPGRADE-3.x.md b/UPGRADE-3.x.md index 892a122d77..8e0c264c58 100644 --- a/UPGRADE-3.x.md +++ b/UPGRADE-3.x.md @@ -1,8 +1,21 @@ UPGRADE 3.x =========== -UPGRADE FROM 3.x to 3.x -======================= +## Deprecated `Sonata\AdminBundle\Model\ModelManagerInterface` collection-related methods. + +Use: +- `new \Doctrine\Common\Collections\ArrayCollection()` instead of `getModelCollectionInstance($class)` +- `$collection->removeElement($element)` instead of `collectionRemoveElement($collection, $element)` +- `$collection->add($element)` instead of `collectionAddElement($collection, $element)` +- `$collection->contains($element)` instead of `collectionHasElement($collection, $element)` +- `$collection->clear()` instead of `collectionClear($collection)` + +UPGRADE FROM 3.73 to 3.74 +========================= + +## Deprecated `Sonata\AdminBundle\Datagrid\ProxyQueryInterface::getSingleScalarResult` + +Use `Sonata\AdminBundle\Datagrid\ProxyQueryInterface::execute` instead. ## The following templates have been deprecated @@ -45,6 +58,10 @@ with `sonata-project/block-bundle` on your composer.json in order to avoid unwan There is a minimal BC Break on `AdminListBlockService`, `AdminSearchBlockService` and `AdminStatsBlockService`. If you are extending those clases (keep in mind that they will become final on 4.0) you should add return type hints to `execute()` and `configureSettings()`. +## Deprecated passing `callable` that does not return `Symfony\Component\Routing\Route` as `$element` (2nd argument) to `Sonata\AdminBundle\Route\RouteCollection::addElement($code, $element)` + +When calling a `Sonata\AdminBundle\Route\RouteCollection::addElement($code, $element)`, please pass `$element` of type `Route|callable():Route`. Passing `callable` that returns non instance of `Route` is deprecated. + UPGRADE FROM 3.72 to 3.73 ========================= diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 75af37cea1..16850aa621 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -128,6 +128,9 @@ If you have implemented a custom admin extension, you must adapt the signature o * `configureBatchActions` * `getAccessMapping` +## AdminHelper +The `AdminHelper::__construct` method changes its `Pool` param to a `PropertyAccessorInterface` one. + ## BreadcrumbsBuilder The `buildBreacrumbs` method may no longer be called from outside the class. diff --git a/composer.json b/composer.json index 5853d209c9..7e31bc8048 100644 --- a/composer.json +++ b/composer.json @@ -23,9 +23,11 @@ ], "require": { "php": "^7.3", - "doctrine/common": "^2.7", + "ext-json": "*", + "doctrine/collections": "^1.6", + "doctrine/common": "^2.7 || ^3.0", "doctrine/inflector": "^1.4 || ^2.0", - "doctrine/persistence": "^1.3.3", + "doctrine/persistence": "^1.3.6 || ^2.0", "knplabs/knp-menu": "^3.1", "knplabs/knp-menu-bundle": "^3.0", "sonata-project/block-bundle": "^4.2", @@ -70,7 +72,9 @@ }, "require-dev": { "matthiasnoback/symfony-dependency-injection-test": "^4.1", + "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpstan": "^0.12.29", + "psalm/plugin-symfony": "^1.4", "psr/event-dispatcher": "^1.0", "sonata-project/intl-bundle": "^2.4", "symfony/browser-kit": "^4.4 || ^5.1", @@ -78,7 +82,8 @@ "symfony/filesystem": "^4.4 || ^5.1", "symfony/maker-bundle": "^1.17", "symfony/phpunit-bridge": "^5.1.1", - "symfony/yaml": "^4.4 || ^5.1" + "symfony/yaml": "^4.4 || ^5.1", + "vimeo/psalm": "^3.13.1" }, "suggest": { "kunicmarko/sonata-auto-configure-bundle": "Auto configures Admin classes", diff --git a/docs/.doctor-rst.yaml b/docs/.doctor-rst.yaml index 29257eb943..641228cde2 100644 --- a/docs/.doctor-rst.yaml +++ b/docs/.doctor-rst.yaml @@ -1,3 +1,7 @@ +# DO NOT EDIT THIS FILE! +# +# It's auto-generated by sonata-project/dev-kit package. + rules: blank_line_after_directive: ~ short_array_syntax: ~ diff --git a/docs/conf.py b/docs/conf.py index 66f99f3eee..09cf99d8ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- + +# DO NOT EDIT THIS FILE! # +# It's auto-generated by sonata-project/dev-kit package. + # IoC documentation build configuration file, created by # sphinx-quickstart on Fri Mar 29 01:43:00 2013. # @@ -44,7 +48,7 @@ master_doc = 'index' # General information about the project. -project = u'' +project = u'Sonata Admin Bundle' copyright = u'2010-2019, Thomas Rabaix' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/reference/routing.rst b/docs/reference/routing.rst index caddc62cfb..a35f4adda0 100644 --- a/docs/reference/routing.rst +++ b/docs/reference/routing.rst @@ -281,7 +281,7 @@ Any single registered route can be removed by name:: Removing all routes except named ones ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you want to disable all default Sonata routes except few whitelisted ones, you can use +If you want to disable all default Sonata routes except few allowed ones, you can use the ``clearExcept()`` method. This method accepts an array of routes you want to keep active:: // src/Admin/MediaAdmin.php diff --git a/docs/requirements.txt b/docs/requirements.txt index a86ef50ae7..ee596d3c45 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,7 @@ # DO NOT EDIT THIS FILE! # # It's auto-generated by sonata-project/dev-kit package. + Sphinx==1.8.5 git+https://github.com/fabpot/sphinx-php.git sphinx_rtd_theme diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0cce313b04..4e75f11fa4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 4 + level: 5 paths: - src diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 01891c55df..36fc24d133 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -32,7 +32,7 @@ It's auto-generated by sonata-project/dev-kit package. - + diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000000..9231c5b801 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,36 @@ + + + + + + DoctrinePersistentCollection + + + + + + $namespace + $class_name + $fields + $fields + $fields + $fields + + + + + $namespace + $class_name + + + + + array_merge(array_flip($keys), $this->filters) + + + + + array_merge(array_flip($keys), $this->elements) + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000000..a02e46da29 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Admin/AbstractAdmin.php b/src/Admin/AbstractAdmin.php index 5772399e3a..11f4aafc88 100644 --- a/src/Admin/AbstractAdmin.php +++ b/src/Admin/AbstractAdmin.php @@ -580,11 +580,7 @@ public function update(object $object): object $extension->preUpdate($this, $object); } - $result = $this->getModelManager()->update($object); - // BC compatibility - if (null !== $result) { - $object = $result; - } + $this->getModelManager()->update($object); $this->postUpdate($object); foreach ($this->extensions as $extension) { @@ -601,11 +597,7 @@ public function create(object $object): object $extension->prePersist($this, $object); } - $result = $this->getModelManager()->create($object); - // BC compatibility - if (null !== $result) { - $object = $result; - } + $this->getModelManager()->create($object); $this->postPersist($object); foreach ($this->extensions as $extension) { @@ -1938,22 +1930,12 @@ public function getSecurityInformation(): array */ public function getPermissionsShow(string $context): array { - switch ($context) { - case self::CONTEXT_DASHBOARD: - case self::CONTEXT_MENU: - default: - return ['LIST']; - } + return ['LIST']; } public function showIn(string $context): bool { - switch ($context) { - case self::CONTEXT_DASHBOARD: - case self::CONTEXT_MENU: - default: - return $this->isGranted($this->getPermissionsShow($context)); - } + return $this->isGranted($this->getPermissionsShow($context)); } public function createObjectSecurity(object $object): void @@ -2249,8 +2231,8 @@ final public function getActionButtons(string $action, ?object $object = null): $buttonList = []; if (\in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'], true) - && $this->hasAccess('create') && $this->hasRoute('create') + && $this->hasAccess('create') ) { $buttonList['create'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_create'), @@ -2258,8 +2240,8 @@ final public function getActionButtons(string $action, ?object $object = null): } if (\in_array($action, ['show', 'delete', 'acl', 'history'], true) - && $this->canAccessObject('edit', $object) && $this->hasRoute('edit') + && $this->canAccessObject('edit', $object) ) { $buttonList['edit'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_edit'), @@ -2267,8 +2249,8 @@ final public function getActionButtons(string $action, ?object $object = null): } if (\in_array($action, ['show', 'edit', 'acl'], true) - && $this->canAccessObject('history', $object) && $this->hasRoute('history') + && $this->canAccessObject('history', $object) ) { $buttonList['history'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_history'), @@ -2277,8 +2259,8 @@ final public function getActionButtons(string $action, ?object $object = null): if (\in_array($action, ['edit', 'history'], true) && $this->isAclEnabled() - && $this->canAccessObject('acl', $object) && $this->hasRoute('acl') + && $this->canAccessObject('acl', $object) ) { $buttonList['acl'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'), @@ -2286,9 +2268,9 @@ final public function getActionButtons(string $action, ?object $object = null): } if (\in_array($action, ['edit', 'history', 'acl'], true) + && $this->hasRoute('show') && $this->canAccessObject('show', $object) && \count($this->getShow()) > 0 - && $this->hasRoute('show') ) { $buttonList['show'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_show'), @@ -2296,8 +2278,8 @@ final public function getActionButtons(string $action, ?object $object = null): } if (\in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'], true) - && $this->hasAccess('list') && $this->hasRoute('list') + && $this->hasAccess('list') ) { $buttonList['list'] = [ 'template' => $this->getTemplateRegistry()->getTemplate('button_list'), diff --git a/src/Admin/AdminHelper.php b/src/Admin/AdminHelper.php index 4eb936ffae..9c933a1a00 100644 --- a/src/Admin/AdminHelper.php +++ b/src/Admin/AdminHelper.php @@ -22,6 +22,7 @@ use Sonata\AdminBundle\Util\FormViewIterator; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** * @final since sonata-project/admin-bundle 3.52 @@ -36,13 +37,13 @@ class AdminHelper private const FORM_FIELD_DELETE = '_delete'; /** - * @var Pool + * @var PropertyAccessorInterface */ - protected $pool; + protected $propertyAccessor; - public function __construct(Pool $pool) + public function __construct(PropertyAccessorInterface $propertyAccessor) { - $this->pool = $pool; + $this->propertyAccessor = $propertyAccessor; } /** @@ -111,11 +112,9 @@ public function appendFormFieldElement(AdminInterface $admin, object $subject, s //Child form not found (probably nested one) //if childFormBuilder was not found resulted in fatal error getName() method call on non object if (!$childFormBuilder) { - $propertyAccessor = $this->pool->getPropertyAccessor(); - $path = $this->getElementAccessPath($elementId, $subject); - $collection = $propertyAccessor->getValue($subject, $path); + $collection = $this->propertyAccessor->getValue($subject, $path); if ($collection instanceof DoctrinePersistentCollection || $collection instanceof PersistentCollection) { //since doctrine 2.4 @@ -127,7 +126,7 @@ public function appendFormFieldElement(AdminInterface $admin, object $subject, s } $collection->add(new $modelClassName()); - $propertyAccessor->setValue($subject, $path, $collection); + $this->propertyAccessor->setValue($subject, $path, $collection); $fieldDescription = null; } else { @@ -195,8 +194,6 @@ public function appendFormFieldElement(AdminInterface $admin, object $subject, s */ public function getElementAccessPath(string $elementId, $model): string { - $propertyAccessor = $this->pool->getPropertyAccessor(); - $idWithoutIdentifier = preg_replace('/^[^_]*_/', '', $elementId); $initialPath = preg_replace('#(_(\d+)_)#', '[$2]_', $idWithoutIdentifier); @@ -208,7 +205,7 @@ public function getElementAccessPath(string $elementId, $model): string $currentPath .= empty($currentPath) ? $part : '_'.$part; $separator = empty($totalPath) ? '' : '.'; - if ($propertyAccessor->isReadable($model, $totalPath.$separator.$currentPath)) { + if ($this->propertyAccessor->isReadable($model, $totalPath.$separator.$currentPath)) { $totalPath .= $separator.$currentPath; $currentPath = ''; } diff --git a/src/Admin/AdminInterface.php b/src/Admin/AdminInterface.php index 87c29f4496..44ddd1a945 100644 --- a/src/Admin/AdminInterface.php +++ b/src/Admin/AdminInterface.php @@ -72,6 +72,8 @@ public function attachAdminClass(FieldDescriptionInterface $fieldDescription): v public function getDatagrid(): DatagridInterface; + public function getPagerType(): string; + /** * Set base controller name. */ @@ -162,7 +164,7 @@ public function setSecurityHandler(SecurityHandlerInterface $securityHandler): v public function getSecurityHandler(): ?SecurityHandlerInterface; /** - * @param string $name + * @param string|array $name */ public function isGranted($name, ?object $object = null): bool; diff --git a/src/Command/GenerateObjectAclCommand.php b/src/Command/GenerateObjectAclCommand.php index 2628114a38..665b6e3013 100644 --- a/src/Command/GenerateObjectAclCommand.php +++ b/src/Command/GenerateObjectAclCommand.php @@ -13,7 +13,7 @@ namespace Sonata\AdminBundle\Command; -use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Persistence\ManagerRegistry; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Admin\Pool; use Sonata\AdminBundle\Util\ObjectAclManipulatorInterface; diff --git a/src/Controller/CRUDController.php b/src/Controller/CRUDController.php index 33c5c194dd..5da8a567f7 100644 --- a/src/Controller/CRUDController.php +++ b/src/Controller/CRUDController.php @@ -470,16 +470,22 @@ public function batchAction(Request $request) $confirmation = $request->get('confirmation', false); - if ($data = json_decode($request->get('data', ''), true)) { + $forwardedRequest = $request->duplicate(); + + if ($data = json_decode((string) $request->get('data', ''), true)) { $action = $data['action']; $idx = $data['idx']; $allElements = (bool) $data['all_elements']; - $request->request->replace(array_merge($request->request->all(), $data)); + $forwardedRequest->request->replace(array_merge($forwardedRequest->request->all(), $data)); } else { - $action = $request->request->getAlnum('action'); + $action = $forwardedRequest->request->getAlnum('action'); $idx = $request->request->get('idx', []); - $allElements = $request->request->getBoolean('all_elements'); - $data = $request->request->all(); + $allElements = $forwardedRequest->request->getBoolean('all_elements'); + + $forwardedRequest->request->set('idx', $idx); + $forwardedRequest->request->set('all_elements', $allElements); + + $data = $forwardedRequest->request->all(); unset($data['_sonata_csrf_token']); } @@ -493,7 +499,7 @@ public function batchAction(Request $request) $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction); if (method_exists($this, $isRelevantAction)) { - $nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $request); + $nonRelevantMessage = $this->$isRelevantAction($idx, $allElements, $forwardedRequest); } else { $nonRelevantMessage = 0 !== \count($idx) || $allElements; // at least one item is selected } @@ -564,7 +570,7 @@ public function batchAction(Request $request) return $this->redirectToList(); } - return $this->$finalAction($query, $request); + return $this->$finalAction($query, $forwardedRequest); } /** diff --git a/src/Datagrid/Datagrid.php b/src/Datagrid/Datagrid.php index e1e0ef5478..d92dc2661b 100644 --- a/src/Datagrid/Datagrid.php +++ b/src/Datagrid/Datagrid.php @@ -199,9 +199,11 @@ static function ($value) { $this->bound = true; } - public function addFilter(FilterInterface $filter): void + public function addFilter(FilterInterface $filter): FilterInterface { $this->filters[$filter->getName()] = $filter; + + return $filter; } public function hasFilter(string $name): bool diff --git a/src/Datagrid/DatagridInterface.php b/src/Datagrid/DatagridInterface.php index a401a19dd3..fcdf80ed54 100644 --- a/src/Datagrid/DatagridInterface.php +++ b/src/Datagrid/DatagridInterface.php @@ -34,7 +34,7 @@ public function getResults(): array; public function buildPager(): void; - public function addFilter(FilterInterface $filter): void; + public function addFilter(FilterInterface $filter): FilterInterface; /** * @return array diff --git a/src/Datagrid/Pager.php b/src/Datagrid/Pager.php index 8f42717b2e..73113856a3 100644 --- a/src/Datagrid/Pager.php +++ b/src/Datagrid/Pager.php @@ -420,6 +420,7 @@ public function next() --$this->resultsCounter; + // NEXT_MAJOR: remove `return` statement, to be compatible with Iterator::next(): void return next($this->results); } @@ -434,6 +435,7 @@ public function rewind() $this->resultsCounter = \count($this->results); + // NEXT_MAJOR: remove `return` statement, to be compatible with Iterator::rewind(): void return reset($this->results); } diff --git a/src/Datagrid/ProxyQueryInterface.php b/src/Datagrid/ProxyQueryInterface.php index 6699d3d647..c69a4264cf 100644 --- a/src/Datagrid/ProxyQueryInterface.php +++ b/src/Datagrid/ProxyQueryInterface.php @@ -40,17 +40,24 @@ public function execute(array $params = [], ?int $hydrationMode = null); */ public function setSortBy(array $parentAssociationMappings, array $fieldMapping): self; - public function getSortBy(): string; + public function getSortBy(): ?string; public function setSortOrder(string $sortOrder): self; - public function getSortOrder(): string; + public function getSortOrder(): ?string; - public function getSingleScalarResult(): ?int; + /** + * NEXT_MAJOR: Remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.74, to be removed in 4.0. + * + * @return mixed + */ + public function getSingleScalarResult(); public function setFirstResult(?int $firstResult): self; - public function getFirstResult(): ?object; + public function getFirstResult(): ?int; public function setMaxResults(?int $maxResults): self; @@ -59,6 +66,8 @@ public function getMaxResults(): ?int; public function getUniqueParameterId(): int; /** + * Join entities from the given association mappings and return the last alias created. + * * @param mixed[] $associationMappings */ public function entityJoin(array $associationMappings): string; diff --git a/src/Form/ChoiceList/ModelChoiceLoader.php b/src/Form/ChoiceList/ModelChoiceLoader.php index e86f2cf6ad..c4cd4fd07a 100644 --- a/src/Form/ChoiceList/ModelChoiceLoader.php +++ b/src/Form/ChoiceList/ModelChoiceLoader.php @@ -28,8 +28,6 @@ */ final class ModelChoiceLoader implements ChoiceLoaderInterface { - public $identifier; - /** * @var \Sonata\AdminBundle\Model\ModelManagerInterface */ @@ -78,8 +76,6 @@ public function __construct( $this->query = $query; $this->choices = $choices; - $this->identifier = $this->modelManager->getIdentifierFieldNames($this->class); - // The property option defines, which property (path) is used for // displaying entities as strings if ($property) { diff --git a/src/Form/DataTransformer/ModelToIdPropertyTransformer.php b/src/Form/DataTransformer/ModelToIdPropertyTransformer.php index a41ebf3957..e763988d1e 100644 --- a/src/Form/DataTransformer/ModelToIdPropertyTransformer.php +++ b/src/Form/DataTransformer/ModelToIdPropertyTransformer.php @@ -13,6 +13,7 @@ namespace Sonata\AdminBundle\Form\DataTransformer; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Util\ClassUtils; use Sonata\AdminBundle\Model\ModelManagerInterface; use Symfony\Component\Form\DataTransformerInterface; @@ -71,7 +72,8 @@ public function __construct( public function reverseTransform($value) { - $collection = $this->modelManager->getModelCollectionInstance($this->className); + /** @var ArrayCollection $collection */ + $collection = new ArrayCollection(); if (empty($value)) { if ($this->multiple) { @@ -94,7 +96,11 @@ public function reverseTransform($value) continue; } - $collection[] = $this->modelManager->find($this->className, $id); + $object = $this->modelManager->find($this->className, $id); + + if (null !== $object) { + $collection->add($object); + } } return $collection; @@ -155,15 +161,13 @@ public function transform($entityOrCollection) } $label = ($this->toStringCallback)($model, $this->property); + } elseif (method_exists($model, '__toString')) { + $label = (string) $model; } else { - try { - $label = (string) $model; - } catch (\Exception $e) { - throw new \RuntimeException(sprintf( - 'Unable to convert the entity %s to String, entity must have a \'__toString()\' method defined', - ClassUtils::getClass($model) - ), 0, $e); - } + throw new \RuntimeException(sprintf( + 'Unable to convert the entity %s to String, entity must have a \'__toString()\' method defined', + ClassUtils::getClass($model) + )); } $result[] = $id; diff --git a/src/Form/DataTransformer/ModelsToArrayTransformer.php b/src/Form/DataTransformer/ModelsToArrayTransformer.php index d899cde5bd..32ff9038f6 100644 --- a/src/Form/DataTransformer/ModelsToArrayTransformer.php +++ b/src/Form/DataTransformer/ModelsToArrayTransformer.php @@ -13,6 +13,7 @@ namespace Sonata\AdminBundle\Form\DataTransformer; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Util\ClassUtils; use Sonata\AdminBundle\Model\ModelManagerInterface; use Sonata\Doctrine\Adapter\AdapterInterface; @@ -67,13 +68,14 @@ public function reverseTransform($keys) throw new UnexpectedTypeException($keys, 'array'); } - $collection = $this->modelManager->getModelCollectionInstance($this->class); + /** @var ArrayCollection $collection */ + $collection = new ArrayCollection(); $notFound = []; // optimize this into a SELECT WHERE IN query foreach ($keys as $key) { if ($model = $this->modelManager->find($this->class, $key)) { - $collection[] = $model; + $collection->add($model); } else { $notFound[] = $key; } diff --git a/src/Form/EventListener/MergeCollectionListener.php b/src/Form/EventListener/MergeCollectionListener.php index 1055b0dcc1..ec78232ac6 100644 --- a/src/Form/EventListener/MergeCollectionListener.php +++ b/src/Form/EventListener/MergeCollectionListener.php @@ -13,6 +13,7 @@ namespace Sonata\AdminBundle\Form\EventListener; +use Doctrine\Common\Collections\Collection; use Sonata\AdminBundle\Model\ModelManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvent; @@ -24,12 +25,23 @@ final class MergeCollectionListener implements EventSubscriberInterface { /** - * @var ModelManagerInterface + * @var ModelManagerInterface|null */ private $modelManager; - public function __construct(ModelManagerInterface $modelManager) + /** + * NEXT_MAJOR: Remove this constructor and the modelManager property. + */ + public function __construct(?ModelManagerInterface $modelManager = null) { + if (null !== $modelManager) { + @trigger_error(sprintf( + 'Passing argument 1 to %s() is deprecated since sonata-project/admin-bundle 3.x' + .' and will be ignored in version 4.0.', + __METHOD__ + ), E_USER_DEPRECATED); + } + $this->modelManager = $modelManager; } @@ -43,7 +55,10 @@ public static function getSubscribedEvents() public function onBind(FormEvent $event): void { $collection = $event->getForm()->getData(); + \assert(null === $collection || $collection instanceof Collection); + $data = $event->getData(); + \assert($data instanceof Collection); // looks like there is no way to remove other listeners $event->stopPropagation(); @@ -51,19 +66,19 @@ public function onBind(FormEvent $event): void if (!$collection) { $collection = $data; } elseif (0 === \count($data)) { - $this->modelManager->collectionClear($collection); + $collection->clear(); } else { // merge $data into $collection foreach ($collection as $model) { - if (!$this->modelManager->collectionHasElement($data, $model)) { - $this->modelManager->collectionRemoveElement($collection, $model); + if (!$data->contains($model)) { + $collection->removeElement($model); } else { - $this->modelManager->collectionRemoveElement($data, $model); + $data->removeElement($model); } } foreach ($data as $model) { - $this->modelManager->collectionAddElement($collection, $model); + $collection->add($model); } } diff --git a/src/Maker/AdminMaker.php b/src/Maker/AdminMaker.php index b489a5a51b..d0f8f54aca 100644 --- a/src/Maker/AdminMaker.php +++ b/src/Maker/AdminMaker.php @@ -39,7 +39,7 @@ final class AdminMaker extends AbstractMaker private $projectDirectory; /** - * @var string[] + * @var array */ private $availableModelManagers; @@ -78,6 +78,10 @@ final class AdminMaker extends AbstractMaker */ private $modelManager; + /** + * @param string $projectDirectory + * @param array $modelManagers + */ public function __construct($projectDirectory, array $modelManagers = []) { $this->projectDirectory = $projectDirectory; @@ -300,6 +304,6 @@ private function configure(InputInterface $input): void } $this->managerType = $input->getOption('manager') ?: array_keys($this->availableModelManagers)[0]; - $this->modelManager = $this->availableModelManagers[$this->managerType] ?: current($this->availableModelManagers); + $this->modelManager = $this->availableModelManagers[$this->managerType] ?? current($this->availableModelManagers); } } diff --git a/src/Manipulator/ObjectManipulator.php b/src/Manipulator/ObjectManipulator.php index 9b4a5e1fd2..5e4634600a 100644 --- a/src/Manipulator/ObjectManipulator.php +++ b/src/Manipulator/ObjectManipulator.php @@ -53,7 +53,7 @@ public static function setObject( object $object, FieldDescriptionInterface $parentFieldDescription ): object { - $mappedBy = $parentFieldDescription->getAssociationMapping()['mappedBy']; + $mappedBy = $parentFieldDescription->getAssociationMapping()['mappedBy'] ?? null; if (null === $mappedBy) { return $instance; } diff --git a/src/Model/AuditManager.php b/src/Model/AuditManager.php index 9442bd454e..e3668b3e28 100644 --- a/src/Model/AuditManager.php +++ b/src/Model/AuditManager.php @@ -21,12 +21,8 @@ final class AuditManager implements AuditManagerInterface { /** - * @var array - */ - private $classes = []; - - /** - * @var array + * @var array + * @phpstan-var array */ private $readers = []; @@ -40,12 +36,12 @@ public function __construct(ContainerInterface $container) $this->container = $container; } - public function setReader($serviceId, array $classes): void + public function setReader(string $serviceId, array $classes): void { $this->readers[$serviceId] = $classes; } - public function hasReader($class) + public function hasReader(string $class): bool { foreach ($this->readers as $classes) { if (\in_array($class, $classes, true)) { @@ -56,7 +52,7 @@ public function hasReader($class) return false; } - public function getReader($class) + public function getReader(string $class): AuditReaderInterface { foreach ($this->readers as $readerId => $classes) { if (\in_array($class, $classes, true)) { diff --git a/src/Model/AuditManagerInterface.php b/src/Model/AuditManagerInterface.php index 878d6d3acf..2cd71f33af 100644 --- a/src/Model/AuditManagerInterface.php +++ b/src/Model/AuditManagerInterface.php @@ -21,27 +21,25 @@ interface AuditManagerInterface /** * Set AuditReaderInterface service id for array of $classes. * - * @param string $serviceId + * @param string[] $classes + * + * @phpstan-param class-string[] $classes */ - public function setReader($serviceId, array $classes); + public function setReader(string $serviceId, array $classes): void; /** * Returns true if $class has AuditReaderInterface. * - * @param string $class - * - * @return bool + * @phpstan-param class-string $class */ - public function hasReader($class); + public function hasReader(string $class): bool; /** * Get AuditReaderInterface service for $class. * - * @param string $class - * * @throws \LogicException * - * @return AuditReaderInterface + * @phpstan-param class-string $class */ - public function getReader($class); + public function getReader(string $class): AuditReaderInterface; } diff --git a/src/Model/AuditReaderInterface.php b/src/Model/AuditReaderInterface.php index 83338353c4..c7a2629cd7 100644 --- a/src/Model/AuditReaderInterface.php +++ b/src/Model/AuditReaderInterface.php @@ -19,46 +19,47 @@ interface AuditReaderInterface { /** - * @param string $className - * @param string $id - * @param string $revision + * @template T of object * - * @return object + * @param mixed $id + * @param mixed $revisionId + * + * @phpstan-param class-string $className + * @phpstan-return T|null */ - public function find($className, $id, $revision); + public function find(string $className, $id, $revisionId): ?object; /** - * @param string $className - * @param int $limit - * @param int $offset - * * @return object[] + * + * @phpstan-param class-string $className */ - public function findRevisionHistory($className, $limit = 20, $offset = 0); + public function findRevisionHistory(string $className, int $limit = 20, int $offset = 0): array; /** - * @param string $classname - * @param string $revision + * @param mixed $revisionId * - * @return object + * @phpstan-param class-string $className */ - public function findRevision($classname, $revision); + public function findRevision(string $className, $revisionId): ?object; /** - * @param string $className - * @param string $id + * @param mixed $id * * @return object[] + * + * @phpstan-param class-string $className */ - public function findRevisions($className, $id); + public function findRevisions(string $className, $id): array; /** - * @param string $className - * @param int $id - * @param int $oldRevision - * @param int $newRevision + * @param mixed $id + * @param mixed $oldRevisionId + * @param mixed $newRevisionId + * + * @return array * - * @return array + * @phpstan-param class-string $className */ - public function diff($className, $id, $oldRevision, $newRevision); + public function diff(string $className, $id, $oldRevisionId, $newRevisionId): array; } diff --git a/src/Model/DatagridManagerInterface.php b/src/Model/DatagridManagerInterface.php index 822f749102..104d37491c 100644 --- a/src/Model/DatagridManagerInterface.php +++ b/src/Model/DatagridManagerInterface.php @@ -19,18 +19,18 @@ interface DatagridManagerInterface { /** - * Return _sort_order, _sort_by, _page and _per_page values. + * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string} * - * @param string $class - * - * @return array + * @phpstan-param class-string $class */ - public function getDefaultSortValues($class); + public function getDefaultSortValues(string $class): array; /** * Return all the allowed _per_page values. * - * @return array + * @return int[] + * + * @phpstan-param class-string $class */ public function getDefaultPerPageOptions(string $class): array; } diff --git a/src/Model/LockInterface.php b/src/Model/LockInterface.php index 5ce55afb07..c03009b142 100644 --- a/src/Model/LockInterface.php +++ b/src/Model/LockInterface.php @@ -21,17 +21,12 @@ interface LockInterface extends ModelManagerInterface { /** - * @param object $object - * - * @return mixed|null + * @return mixed */ - public function getLockVersion($object); + public function getLockVersion(object $object); /** - * @param object $object - * @param mixed $expectedVersion - * * @throws LockException */ - public function lock($object, $expectedVersion); + public function lock(object $object, ?int $expectedVersion): void; } diff --git a/src/Model/ModelManagerInterface.php b/src/Model/ModelManagerInterface.php index bc5b7c32c2..2bbd1758b5 100644 --- a/src/Model/ModelManagerInterface.php +++ b/src/Model/ModelManagerInterface.php @@ -13,6 +13,7 @@ namespace Sonata\AdminBundle\Model; +use Doctrine\Common\Collections\Collection; use Sonata\AdminBundle\Admin\FieldDescriptionInterface; use Sonata\AdminBundle\Datagrid\DatagridInterface; use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; @@ -25,70 +26,74 @@ interface ModelManagerInterface extends DatagridManagerInterface { /** - * @param string $class - * @param string $name + * @param array $options * - * @return FieldDescriptionInterface + * @phpstan-param class-string $class */ - public function getNewFieldDescriptionInstance($class, $name, array $options = []); + public function getNewFieldDescriptionInstance(string $class, string $name, array $options = []): FieldDescriptionInterface; /** - * @param object $object - * * @throws ModelManagerException */ - public function create($object); + public function create(object $object): void; /** - * @param object $object - * * @throws ModelManagerException */ - public function update($object); + public function update(object $object): void; /** - * @param object $object - * * @throws ModelManagerException */ - public function delete($object); + public function delete(object $object): void; /** - * @param string $class + * @template T of object + * + * @param array $criteria + * + * @return object[] all objects matching the criteria * - * @return array all objects matching the criteria + * @phpstan-param class-string $class + * @phpstan-return T[] */ - public function findBy($class, array $criteria = []); + public function findBy(string $class, array $criteria = []): array; /** - * @param string $class + * @template T of object + * + * @param array $criteria * * @return object|null an object matching the criteria or null if none match + * + * @phpstan-param class-string $class + * @phpstan-return T|null */ - public function findOneBy($class, array $criteria = []); + public function findOneBy(string $class, array $criteria = []): ?object; /** - * @param string $class - * @param mixed $id + * @template T of object + * + * @param mixed $id * * @return object|null the object with id or null if not found + * + * @phpstan-param class-string $class + * @phpstan-return T|null */ - public function find($class, $id); + public function find(string $class, $id): ?object; /** - * @param string $class - * * @throws ModelManagerException + * + * @phpstan-param class-string $class */ - public function batchDelete($class, ProxyQueryInterface $queryProxy); + public function batchDelete(string $class, ProxyQueryInterface $queryProxy): void; /** - * @param string $class - * @param string $alias - * - * @return ProxyQueryInterface + * @phpstan-param class-string $class */ - public function createQuery($class, $alias = 'o'); + public function createQuery(string $class, string $alias = 'o'): ProxyQueryInterface; /** * Get the identifiers of this model class. @@ -97,135 +102,159 @@ public function createQuery($class, $alias = 'o'); * composed of multiple columns. If you need a string representation, * use getNormalizedIdentifier resp. getUrlSafeIdentifier * - * @param object $model - * - * @return array list of all identifiers of this model + * @return mixed[] */ - public function getIdentifierValues($model); + public function getIdentifierValues(object $model): array; /** - * Get a list of the field names models of the specified class use to store - * the identifier. + * Get a list of the field names models of the specified fully qualified + * class name used to store the identifier. * - * @param string $class fully qualified class name + * @return string[] * - * @return array + * @phpstan-param class-string $class */ - public function getIdentifierFieldNames($class); + public function getIdentifierFieldNames(string $class): array; /** * Get the identifiers for this model class as a string. - * - * @param object $model - * - * @return string a string representation of the identifiers for this - * instance */ - public function getNormalizedIdentifier($model); + public function getNormalizedIdentifier(object $model): string; /** * Get the identifiers as a string that is safe to use in a url. * * This is similar to getNormalizedIdentifier but guarantees an id that can * be used in a URL. - * - * @param object $model - * - * @return string string representation of the id that is safe to use in a url */ - public function getUrlSafeIdentifier($model); + public function getUrlSafeIdentifier(object $model): string; /** * Create a new instance of the model of the specified class. * - * @param string $class + * @template T of object * - * @return object + * @phpstan-param class-string $class + * @phpstan-return T */ - public function getModelInstance($class); + public function getModelInstance(string $class): object; /** - * @param string $class + * NEXT_MAJOR: Remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.x. To be removed in 4.0. Use doctrine/collections instead. * - * @return array|\ArrayAccess + * @template T of object + * + * @return Collection + * + * @phpstan-param class-string $class + * @phpstan-return Collection */ - public function getModelCollectionInstance($class); + public function getModelCollectionInstance(string $class): Collection; /** + * NEXT_MAJOR: Remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.x. To be removed in 4.0. Use doctrine/collections instead. + * * Removes an element from the collection. * - * @param array $collection - * @param object $element + * @template T of object + * + * @param Collection $collection + * + * @phpstan-param Collection $collection + * @phpstan-param T $element */ - public function collectionRemoveElement(&$collection, &$element); + public function collectionRemoveElement(Collection $collection, object $element): void; /** + * NEXT_MAJOR: Remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.x. To be removed in 4.0. Use doctrine/collections instead. + * * Add an element from the collection. * - * @param array $collection - * @param object $element + * @template T of object + * + * @param Collection $collection + * + * @phpstan-param Collection $collection + * @phpstan-param T $element */ - public function collectionAddElement(&$collection, &$element); + public function collectionAddElement(Collection $collection, object $element): void; /** + * NEXT_MAJOR: Remove this method. + * + * @deprecated since sonata-project/admin-bundle 3.x. To be removed in 4.0. Use doctrine/collections instead. + * * Check if the element exists in the collection. * - * @param array $collection - * @param object $element + * @template T of object + * + * @param Collection $collection * - * @return bool + * @phpstan-param Collection $collection + * @phpstan-param T $element */ - public function collectionHasElement(&$collection, &$element); + public function collectionHasElement(Collection $collection, object $element): bool; /** * Clear the collection. * - * @param array $collection + * @param Collection $collection + * + * @phpstan-param Collection $collection */ - public function collectionClear(&$collection); + public function collectionClear(Collection $collection): void; /** - * @param string $class + * @template T of object + * + * @param array $array * - * @return object + * @phpstan-param class-string $class + * @phpstan-return T */ - public function modelReverseTransform($class, array $array = []); + public function modelReverseTransform(string $class, array $array = []): object; /** - * @param string $class - * @param object $instance + * @template T of object * - * @return object + * @phpstan-param class-string $class + * @phpstan-param T $instance + * @phpstan-return T */ - public function modelTransform($class, $instance); + public function modelTransform(string $class, object $instance): object; /** - * @param mixed $query + * @return mixed */ - public function executeQuery($query); + public function executeQuery(object $query); /** - * @param int|null $firstResult - * @param int|null $maxResult - * - * @return SourceIteratorInterface + * @param string[] $fields */ public function getDataSourceIterator( DatagridInterface $datagrid, array $fields, - $firstResult = null, - $maxResult = null - ); + ?int $firstResult = null, + ?int $maxResult = null + ): SourceIteratorInterface; /** - * @param string $class - * * @return string[] + * + * @phpstan-param class-string $class */ - public function getExportFields($class); + public function getExportFields(string $class): array; /** - * @param string $class + * @param string[] $idx + * + * @phpstan-param class-string $class */ - public function addIdentifiersToQuery($class, ProxyQueryInterface $query, array $idx); + public function addIdentifiersToQuery(string $class, ProxyQueryInterface $query, array $idx): void; } diff --git a/src/Resources/config/core.xml b/src/Resources/config/core.xml index b5ec5438e3..4fcd723b6c 100644 --- a/src/Resources/config/core.xml +++ b/src/Resources/config/core.xml @@ -18,7 +18,7 @@ - + @@ -74,7 +74,6 @@ - %sonata.admin.configuration.global_search.case_sensitive% diff --git a/src/Resources/views/CRUD/base_list.html.twig b/src/Resources/views/CRUD/base_list.html.twig index cff1e0e624..f5cee2f188 100644 --- a/src/Resources/views/CRUD/base_list.html.twig +++ b/src/Resources/views/CRUD/base_list.html.twig @@ -192,7 +192,7 @@ file that was distributed with this source code.