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

#7698 - modularize admin global search #9192

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,27 @@ class GlobalSearch extends \Magento\Backend\Controller\Adminhtml\Index
*/
protected $_searchModules;

/**
* modules that support preview
Copy link
Contributor

@maghamed maghamed Jul 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw this controller should be marked with PHP DocBlock as @ api
because now it represents extension point for other search modules.

Btw it makes sense to use another extension point for these purposes

*
* @var array
*/
protected $previewModules;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use protected for newly created properties


/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
* @param array $searchModules
* @param array $previewModules
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
array $searchModules = []
array $searchModules = [],
array $previewModules = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such changes to the class constructor violate backwards compatibility policy:

  • New dependencies must not be added to the middle, only as a last parameter.
  • All newly added arguments must be nullable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like

array $searchModules = [],
ItemFactory $itemFactory = null,
SearchCriteriaFactory $criteriaFactory = null,
array $previewModules = []

And then loading the optional constructor if null via $newDependency ?: \Magento\Framework\App\ObjectManager::getInstance()->get(\New\Dependency\Interface::class) would work well here I think.

) {
$this->_searchModules = $searchModules;
$this->previewModules = $previewModules;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider extracting this logic to a dedicated model, since It does not look like it is in controllers responsibility.
Magento 2 is aiming to implement controllers as lightweight as possible, moving all possible parts outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ishakhsuvarov I agree whit this, but I don't see a solution to move the search logic to a separate class and keep backwards compatibility (as suggested in other comments) at the same time. Maybe I can move this to a separate class, and you release it when you break BC for something else anyway.

parent::__construct($context);
$this->resultJsonFactory = $resultJsonFactory;
}
Expand All @@ -52,7 +62,11 @@ public function execute()
'description' => __('You need more permissions to do this.'),
];
} else {
if (empty($this->_searchModules)) {
$previewItems = $this->getPreviewItems();
$searchItems = $this->getSearchItems();
$items = array_merge_recursive($items, $previewItems, $searchItems);

if (empty($items)) {
$items[] = [
'id' => 'error',
'type' => __('Error'),
Expand All @@ -61,34 +75,64 @@ public function execute()
'Please make sure that all global admin search modules are installed and activated.'
),
];
} else {
$start = $this->getRequest()->getParam('start', 1);
$limit = $this->getRequest()->getParam('limit', 10);
$query = $this->getRequest()->getParam('query', '');
foreach ($this->_searchModules as $searchConfig) {
if ($searchConfig['acl'] && !$this->_authorization->isAllowed($searchConfig['acl'])) {
continue;
}

$className = $searchConfig['class'];
if (empty($className)) {
continue;
}
$searchInstance = $this->_objectManager->create($className);
$results = $searchInstance->setStart(
$start
)->setLimit(
$limit
)->setQuery(
$query
)->load()->getResults();
$items = array_merge_recursive($items, $results);
}
}
}

/** @var \Magento\Framework\Controller\Result\Json $resultJson */
$resultJson = $this->resultJsonFactory->create();
return $resultJson->setData($items);
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please provide a clear description for this function

* @return array
*/
protected function getPreviewItems()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's give just "private" visibility for all the new functions

{
$result = [];
$query = $this->getRequest()->getParam('query', '');
foreach ($this->previewModules as $previewConfig) {
if ($previewConfig['acl'] && !$this->_authorization->isAllowed($previewConfig['acl'])) {
continue;
}
if (!$previewConfig['url'] || !$previewConfig['text']) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not correct comparison
!$previewConfig['url'] || !$previewConfig['text']

use isset instead

continue;
}
$result[] = [
'url' => $this->getUrl($previewConfig['url']).'?search='.$query,
'name' => __($previewConfig['text'], $query)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have translate="true" attribute in DI.xml , as we have for UI components configuration, thus translation would not work correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maghamed Any pointers on how to handle this so the translation will work and still be able to keep the texts in DI? O maybe move them somewhere else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tzyganu I found a possibility to specify "string" type to be translatable in DI.xml
using the "translate" attribute
Here is the definition of this attribute for any xsi:type="string" attribute type in types.XSD schema which is used by ObjectManager

<xs:extension base="argumentType">
<xs:attribute name="translate" use="optional" type="xs:boolean"/>
</xs:extension>

Here is how this attribute interpreted

$needTranslation = isset($data['translate']) ? $this->booleanUtils->toBoolean($data['translate']) : false;
if ($needTranslation) {
$result = (string)new \Magento\Framework\Phrase($result);
}

But there is no any usage of this attribute in Magento.
And looks like all the tries to pass strings using DI.xml just don't translate at all

Like this one:

    <type name="Magento\Catalog\Model\Entity\Product\Attribute\Design\Options\Container">
        <arguments>
            <argument name="options" xsi:type="array">
                <item name="option1" xsi:type="array">
                    <item name="value" xsi:type="string">container1</item>
                    <item name="label" xsi:type="string">Product Info Column</item>
                </item>
                <item name="option2" xsi:type="array">
                    <item name="value" xsi:type="string">container2</item>
                    <item name="label" xsi:type="string">Block after Info Column</item>
                </item>
            </argument>
        </arguments>
    </type>

or this one:

    <type name="Magento\Customer\Ui\Component\MassAction\Group\Options">
        <arguments>
            <argument name="data" xsi:type="array">
                <item name="urlPath" xsi:type="string">customer/index/massAssignGroup</item>
                <item name="paramName" xsi:type="string">group</item>
                <item name="confirm" xsi:type="array">
                    <item name="title" xsi:type="string">Assign a Customer Group</item>
                    <item name="message" xsi:type="string">Are you sure to assign selected customers to new group?</item>
                </item>
            </argument>
        </arguments>
    </type>

];
}
return $result;
}

/**
* @return array
*/
protected function getSearchItems()
{
$items = [];
$start = $this->getRequest()->getParam('start', 1);
$limit = $this->getRequest()->getParam('limit', 10);
$query = $this->getRequest()->getParam('query', '');
foreach ($this->_searchModules as $searchConfig) {
if ($searchConfig['acl'] && !$this->_authorization->isAllowed($searchConfig['acl'])) {
continue;
}

$className = $searchConfig['class'];
if (empty($className)) {
continue;
}
$searchInstance = $this->_objectManager->create($className);
Copy link
Contributor

@maghamed maghamed Jul 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we use ObjectManager here?
the dedicated factory should be used instead. Btw, this factory should check the type of needed object . All the search classes should implement common interface/provide the same contract
Because for now there is no any guarantee that created class supports contract by which you work with it below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maghamed I didn't add this by myself. The code was there I just moved it around. But I will try to find a way around it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marius, you right this looks like legacy code, even taking into account that it was written quite recently.

What we usually do for such cases is to introduce new Interface with the common contract, and Factory which is responsible for producing an instance of this interface and making sure that object which would be created is an instance of the interface mentioned above.
Factory also could serve as an extension point and other developers could introduce own instance of the contract and register it using DI configuration in this factory (constructor argument).

Doing so we get:

  • well defined interface for some piece of functionality
  • well defined Factory which creates some particular implementation of this interface
  • extension point which helps 3rd party developers to extend basic implementations with custom one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maghamed already on it.

$results = $searchInstance->setStart(
$start
)->setLimit(
$limit
)->setQuery(
$query
)->load()->getResults();
$items = array_merge_recursive($items, $results);
}
return $items;
}
}
12 changes: 2 additions & 10 deletions app/code/Magento/Backend/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,8 @@
</type>
<type name="Magento\Backend\Controller\Adminhtml\Index\GlobalSearch">
<arguments>
<argument name="searchModules" xsi:type="array">
<item name="customers" xsi:type="array">
<item name="class" xsi:type="string">Magento\Backend\Model\Search\Customer</item>
<item name="acl" xsi:type="string">Magento_Customer::customer</item>
</item>
<item name="sales" xsi:type="array">
<item name="class" xsi:type="string">Magento\Backend\Model\Search\Order</item>
<item name="acl" xsi:type="string">Magento_Sales::sales</item>
</item>
</argument>
<argument name="searchModules" xsi:type="array" />
<argument name="previewModules" xsi:type="array" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not mandatory to provide these configurations

</arguments>
</type>
<virtualType name="Magento\Backend\Model\Auth\Session\Storage" type="Magento\Framework\Session\Storage">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,7 @@
</form>
<script data-template="search-suggest" type="text/x-magento-template">
<ul class="search-global-menu">
<li class="item">
<a id="searchPreviewProducts" href="<?php /* @escapeNotVerified */ echo $block->getURL('catalog/product/index/'); ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Products</a>
</li>
<li class="item">
<a id="searchPreviewOrders" href="<?php /* @escapeNotVerified */ echo $block->getURL('sales/order/index/'); ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Orders</a>
</li>
<li class="item">
<a id="searchPreviewCustomers" href="<?php /* @escapeNotVerified */ echo $block->getURL('customer/index/index/'); ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Customers</a>
</li>
<li class="item">
<a id="searchPreviewPages" href="<?php /* @escapeNotVerified */ echo $block->getURL('cms/page/index/'); ?>?search=<%- data.term%>" class="title">"<%- data.term%>" in Pages</a>
</li>
<% if (data.items.length) { %>
<% if (data.items.length) { %>
<% _.each(data.items, function(value){ %>
<li class="item"
<%- data.optionData(value) %>
Expand Down
10 changes: 0 additions & 10 deletions app/code/Magento/CatalogSearch/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,6 @@
<argument name="scope" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument>
</arguments>
</type>
<type name="Magento\Backend\Controller\Adminhtml\Index\GlobalSearch">
<arguments>
<argument name="searchModules" xsi:type="array">
<item name="products" xsi:type="array">
<item name="class" xsi:type="string">Magento\CatalogSearch\Model\Search\Catalog</item>
<item name="acl" xsi:type="string">Magento_Catalog::catalog</item>
</item>
</argument>
</arguments>
</type>
<preference for="Magento\Search\Model\SearchCollectionInterface" type="Magento\CatalogSearch\Model\ResourceModel\Search\Collection" />
<type name="Magento\Catalog\Model\Layer\Search\CollectionFilter">
<plugin name="searchQuery" type="Magento\CatalogSearch\Model\Layer\Search\Plugin\CollectionFilter" />
Expand Down
11 changes: 11 additions & 0 deletions app/code/Magento/Cms/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@
</argument>
</arguments>
</type>
<type name="Magento\Backend\Controller\Adminhtml\Index\GlobalSearch">
<arguments>
<argument name="previewModules" xsi:type="array">
<item name="cms-pages" xsi:type="array">
<item name="url" xsi:type="string">cms/page/index</item>
<item name="text" xsi:type="string">'%1' in Pages</item>
<item name="acl" xsi:type="string">Magento_Cms::page</item>
</item>
</argument>
</arguments>
</type>
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
Expand Down
1 change: 1 addition & 0 deletions app/code/Magento/Cms/etc/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<sequence>
<module name="Magento_Store"/>
<module name="Magento_Theme"/>
<module name="Magento_Backend" />
</sequence>
</module>
</config>
18 changes: 18 additions & 0 deletions app/code/Magento/Customer/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@
</argument>
</arguments>
</type>
<type name="Magento\Backend\Controller\Adminhtml\Index\GlobalSearch">
<arguments>
<argument name="searchModules" xsi:type="array">
<item name="customers" xsi:type="array">
<item name="class" xsi:type="string">Magento\Backend\Model\Search\Customer</item>
<item name="acl" xsi:type="string">Magento_Customer::customer</item>
</item>
</argument>
<argument name="previewModules" xsi:type="array">
<item name="customers" xsi:type="array">
<item name="url" xsi:type="string">customer/index/index</item>
<item name="text" xsi:type="string">'%1' in Customers</item>
<item name="acl" xsi:type="string">Magento_Customer::customer</item>
</item>
</argument>
</arguments>
</type>

<type name="Magento\Customer\Model\ResourceModel\Address">
<arguments>
<argument name="customerRepository" xsi:type="object">Magento\Customer\Api\CustomerRepositoryInterface\Proxy</argument>
Expand Down
1 change: 1 addition & 0 deletions app/code/Magento/Customer/etc/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<sequence>
<module name="Magento_Eav"/>
<module name="Magento_Directory"/>
<module name="Magento_Backend" />
</sequence>
</module>
</config>
17 changes: 17 additions & 0 deletions app/code/Magento/Sales/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@
<preference for="Magento\Sales\Api\RefundOrderInterface" type="Magento\Sales\Model\RefundOrder"/>
<preference for="Magento\Sales\Api\RefundInvoiceInterface" type="Magento\Sales\Model\RefundInvoice"/>
<preference for="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProviderInterface" type="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProvider" />
<type name="Magento\Backend\Controller\Adminhtml\Index\GlobalSearch">
<arguments>
<argument name="searchModules" xsi:type="array">
<item name="sales" xsi:type="array">
<item name="class" xsi:type="string">Magento\Backend\Model\Search\Order</item>
<item name="acl" xsi:type="string">Magento_Sales::sales</item>
</item>
</argument>
<argument name="previewModules" xsi:type="array">
<item name="sales" xsi:type="array">
<item name="url" xsi:type="string">sales/order/index</item>
<item name="text" xsi:type="string">'%1' in Orders</item>
<item name="acl" xsi:type="string">Magento_Sales::sales</item>
</item>
</argument>
</arguments>
</type>
<type name="Magento\Sales\Model\ResourceModel\Provider\NotSyncedDataProvider">
<arguments>
<argument name="providers" xsi:type="array">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ public function testGlobalSearchAction()
$this->dispatch('backend/admin/index/globalSearch');

$actual = $this->getResponse()->getBody();
$this->assertEquals([], json_decode($actual));
$this->assertEquals(3, count(json_decode($actual, true)));
}
}