Skip to content

Product List / CMS Page & Block List cannot be opened in admin area when plugin used #31362

Closed
@HenKun

Description

@HenKun

OptionSourceInterface $optionsProvider = null,

Summary (*)

Product List / CMS Page & Block List cannot be opened in admin area when a plugin is used for Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset or one of its parents in the hierarchy.

Vanilla Magento 2.4.1
Tested on PHP 7.4

Exception message:

Error: Cannot instantiate interface Magento\Framework\Data\OptionSourceInterface in /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:50 Stack trace: 
#0 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/ObjectManager.php(70): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() 
#1 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(170): Magento\Framework\ObjectManager\ObjectManager->get() 
#2 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(276): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() 
#3 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(239): Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() 
#4 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(34): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() 
#5 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(59): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() 
#6 /home/cloudpanel/htdocs/html/vendor/magento/framework/ObjectManager/ObjectManager.php(56): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() 
#7 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Element/UiComponentFactory.php(173): Magento\Framework\ObjectManager\ObjectManager->create() 
#8 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Element/UiComponentFactory.php(151): Magento\Framework\View\Element\UiComponentFactory->createChildComponent() 
#9 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Element/UiComponentFactory.php(151): Magento\Framework\View\Element\UiComponentFactory->createChildComponent() 
#10 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Element/UiComponentFactory.php(248): Magento\Framework\View\Element\UiComponentFactory->createChildComponent() 
#11 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout/Generator/UiComponent.php(140): Magento\Framework\View\Element\UiComponentFactory->create() 
#12 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout/Generator/UiComponent.php(103): Magento\Framework\View\Layout\Generator\UiComponent->generateComponent() 
#13 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout/GeneratorPool.php(93): Magento\Framework\View\Layout\Generator\UiComponent->process() 
#14 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout.php(352): Magento\Framework\View\Layout\GeneratorPool->process() 
#15 /home/cloudpanel/htdocs/html/generated/code/Magento/Framework/View/Layout/Interceptor.php(68): Magento\Framework\View\Layout->generateElements() 
#16 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout/Builder.php(129): Magento\Framework\View\Layout\Interceptor->generateElements() 
#17 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Page/Builder.php(65): Magento\Framework\View\Layout\Builder->generateLayoutBlocks() 
#18 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout/Builder.php(65): Magento\Framework\View\Page\Builder->generateLayoutBlocks() 
#19 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout.php(259): Magento\Framework\View\Layout\Builder->build() 
#20 /home/cloudpanel/htdocs/html/vendor/magento/framework/View/Layout.php(884): Magento\Framework\View\Layout->build() 
#21 /home/cloudpanel/htdocs/html/generated/code/Magento/Framework/View/Layout/Interceptor.php(293): Magento\Framework\View\Layout->getBlock() 
#22 /home/cloudpanel/htdocs/html/vendor/magento/module-backend/Model/View/Result/Page.php(26): Magento\Framework\View\Layout\Interceptor->getBlock() 
#23 /home/cloudpanel/htdocs/html/generated/code/Magento/Backend/Model/View/Result/Page/Interceptor.php(23): Magento\Backend\Model\View\Result\Page->setActiveMenu() 
#24 /home/cloudpanel/htdocs/html/vendor/magento/module-catalog/Controller/Adminhtml/Product/Index.php(41): Magento\Backend\Model\View\Result\Page\Interceptor->setActiveMenu() 
#25 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Catalog\Controller\Adminhtml\Product\Index->execute() 
#26 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->___callParent() 
#27 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/Action/Plugin/ActionFlagNoDispatchPlugin.php(51): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->Magento\Framework\Interception\{closure}() 
#28 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Framework\App\Action\Plugin\ActionFlagNoDispatchPlugin->aroundExecute() 
#29 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->Magento\Framework\Interception\{closure}() 
#30 /home/cloudpanel/htdocs/html/generated/code/Magento/Catalog/Controller/Adminhtml/Product/Index/Interceptor.php(23): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->___callPlugins() 
#31 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/Action/Action.php(111): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->execute() 
#32 /home/cloudpanel/htdocs/html/vendor/magento/module-backend/App/AbstractAction.php(151): Magento\Framework\App\Action\Action->dispatch() 
#33 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Backend\App\AbstractAction->dispatch() 
#34 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->___callParent() 
#35 /home/cloudpanel/htdocs/html/vendor/magento/module-backend/App/Action/Plugin/Authentication.php(143): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->Magento\Framework\Interception\{closure}() 
#36 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Backend\App\Action\Plugin\Authentication->aroundDispatch() 
#37 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->Magento\Framework\Interception\{closure}() 
#38 /home/cloudpanel/htdocs/html/generated/code/Magento/Catalog/Controller/Adminhtml/Product/Index/Interceptor.php(32): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->___callPlugins() 
#39 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/FrontController.php(186): Magento\Catalog\Controller\Adminhtml\Product\Index\Interceptor->dispatch() 
#40 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/FrontController.php(118): Magento\Framework\App\FrontController->processRequest() 
#41 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\FrontController->dispatch() 
#42 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\App\FrontController\Interceptor->___callParent() 
#43 /home/cloudpanel/htdocs/html/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() 
#44 /home/cloudpanel/htdocs/html/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\Framework\App\FrontController\Interceptor->___callPlugins() 
#45 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/Http.php(116): Magento\Framework\App\FrontController\Interceptor->dispatch() 
#46 /home/cloudpanel/htdocs/html/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\Framework\App\Http->launch() 
#47 /home/cloudpanel/htdocs/html/vendor/magento/framework/App/Bootstrap.php(263): Magento\Framework\App\Http\Interceptor->launch() 
#48 /home/cloudpanel/htdocs/html/pub/index.php(40): Magento\Framework\App\Bootstrap->run() 
#49 {main}

Examples (*)

  1. Create a plugin for Asset or one of its parents, e.g. in Vendor\Module\etc\adminhtml\di.xml
<type name="Magento\Framework\View\Element\UiComponentInterface">
        <plugin name="samplePlugin"
                type="Vendor\Module\Plugin\View\Element\UiComponentInterfacePlugin"/>
</type>
  1. Clear cache / compile (if in production mode)
  2. Open Admin Panel and go to Catalog > Products or Content > Cms or Content > Blocks
  3. See the error

Findings

The problem lies in the constructor of Asset on line 47 in app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php

Here an optional value is given for a parameter before a required parameter is stated. Although this is allowed in PHP < 8, this is handled in a special way, when the Interceptor is generated.

As stated in https://www.php.net/manual/en/functions.arguments.php:

One exception to this rule are arguments of the form Type $param = null, where the null default makes the type implicitly nullable.

So the Interceptor constructor for Asset\Interceptor in generated/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset/Interceptor.php looks like:

public function __construct(\Magento\Framework\View\Element\UiComponent\ContextInterface $context, \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory, \Magento\Framework\Api\FilterBuilder $filterBuilder, \Magento\Ui\Component\Filters\FilterModifier $filterModifier, ?\Magento\Framework\Data\OptionSourceInterface $optionsProvider, \Magento\MediaContentApi\Api\GetContentByAssetIdsInterface $getContentIdentities, array $components = [], array $data = [])

As you can see, the type of the $optionsProvider was implicitely converted to a nullable type, while removing the default value null.

This does not happen, when there is no required parameter after the optional parameter, as you can see in the constructor of Select\Interceptor (Select is the direct parent of Asset) in generated/code/Magento/Ui/Component/Filters/Type/Select/Interceptor.php:

public function __construct(\Magento\Framework\View\Element\UiComponent\ContextInterface $context, \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory, \Magento\Framework\Api\FilterBuilder $filterBuilder, \Magento\Ui\Component\Filters\FilterModifier $filterModifier, ?\Magento\Framework\Data\OptionSourceInterface $optionsProvider = null, array $components = [], array $data = [])

The default value null is kept for the $optionsProvider parameter.

This leads to the situation, that Asset\Interceptor cannot be constructed. In vendor/magento/framework/Code/Reader/ClassReader.php on line 35 the parameter is checked to be required or not by checking isOptional and isDefaultValueAvailable on the ReflectionParameter. Both now return false for $optionsProvider parameter in Asset\Interceptor because the parameter is indeed neither optional anymore nor does it have a default value anymore. (Nullable types are not considered optional, nor is null the default value for nullable types). So in the end, the parameter is considered required.

Going further we have to differ in production and developer mode.

In production mode it is now checked in vendor/magento/framework/ObjectManager/Factory/Dynamic/Production.php on line 33 if the parameter required. If so, the type (OptionSourceInterface) is used as instance type instead of using the actual default null. This won't work of course.

Similar behavior in development mode, just that the check happens in vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php on line 259

Proposed solution

The constructor of vendor/magento/module-media-gallery-ui/Ui/Component/Listing/Filters/Asset.php must be changed from:

public function __construct(
    ContextInterface $context,
    UiComponentFactory $uiComponentFactory,
    FilterBuilder $filterBuilder,
    FilterModifier $filterModifier,
    OptionSourceInterface $optionsProvider = null,
    GetContentByAssetIdsInterface $getContentIdentities,
    array $components = [],
    array $data = []
)

to

public function __construct(
    ContextInterface $context,
    UiComponentFactory $uiComponentFactory,
    FilterBuilder $filterBuilder,
    FilterModifier $filterModifier,
    GetContentByAssetIdsInterface $getContentIdentities,
    OptionSourceInterface $optionsProvider = null,
    array $components = [],
    array $data = []
)

Please provide Severity assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.

  • Severity: S0 - Affects critical data or functionality and leaves users with no workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions