-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Changes from 6 commits
36f3410
3af1834
cec4e90
ce0d756
a0d3bc2
5c5d676
3391728
4a3090c
ce31871
3e5a937
4dc0667
6f26c3e
820c116
f95d6e2
fa050cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
/** | ||
* | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\Backend\Api\Search; | ||
|
||
/** | ||
* @api | ||
*/ | ||
interface ItemsInterface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest renaming this interface, as Items is too broad definition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ishakhsuvarov do you have a suggestion for @tzyganu in this case? I could think that |
||
{ | ||
const LIMIT = 'limit'; | ||
const START = 'start'; | ||
const QUERY = 'query'; | ||
|
||
/** | ||
* get the search result items | ||
* | ||
* @return array | ||
*/ | ||
public function getResults(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This interface is pretty bad one because it has a temporal coupling BTW This interface looks like a good application of Builder pattern for me. |
||
|
||
/** | ||
* set offset | ||
* | ||
* @param int $start | ||
* @return ItemsInterface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't recommend to use "fluent interfaces" and "method chaining", we support CQS where the command should return void instead of $this. Thus, there are two options @tzyganu :
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @maghamed I was trying the SearchCriteria approach, but hit a wall. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tzyganu I think the existing SearchCriteria we have in Framework \Magento\Framework\Api\SearchCriteria would not fit your needs. It's too heavyweight for your specific case.
namespace Your\Custom\Api;
class SearchCriteria
{
public function setStart($start);
public function setQuery($query);
public function setLimit($limit);
public function getStart();
public function getQuery();
public function getLimit();
}
namespace Your\Custom\Api;
class QueryProcessor
{
public function getResults(SearchCriteria $searchCriteria); // or maybe Search(SearchCriteria $searchCriteria)
} Doing so we make a responsibility segregation and don't have problems with managing and keeping the state in entity responsible for search request execution because all the parameters come as input from the outside each time. |
||
*/ | ||
public function setStart($start); | ||
|
||
/** | ||
* set search query | ||
* | ||
* @param string $query | ||
* @return ItemsInterface | ||
*/ | ||
public function setQuery($query); | ||
|
||
/** | ||
* set limit | ||
* | ||
* @param int $limit | ||
* @return ItemsInterface | ||
*/ | ||
public function setLimit($limit); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,6 +6,11 @@ | |||||||||||||||
*/ | ||||||||||||||||
namespace Magento\Backend\Controller\Adminhtml\Index; | ||||||||||||||||
|
||||||||||||||||
use Magento\Backend\Model\Search\ItemsFactory; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* @api | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. everything marked as @ api should be accompanied with description There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Current Controller became big enough and now contains Business logic inside. In M2 we are trying to make Controllers as light/ thin as possible This Handler would be marked as @ api intead of current controller |
||||||||||||||||
*/ | ||||||||||||||||
class GlobalSearch extends \Magento\Backend\Controller\Adminhtml\Index | ||||||||||||||||
{ | ||||||||||||||||
/** | ||||||||||||||||
|
@@ -20,17 +25,34 @@ class GlobalSearch extends \Magento\Backend\Controller\Adminhtml\Index | |||||||||||||||
*/ | ||||||||||||||||
protected $_searchModules; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* modules that support preview | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw this controller should be marked with PHP DocBlock as @ api Btw it makes sense to use another extension point for these purposes |
||||||||||||||||
* | ||||||||||||||||
* @var array | ||||||||||||||||
*/ | ||||||||||||||||
private $previewModules; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* @var ItemsFactory | ||||||||||||||||
*/ | ||||||||||||||||
private $itemsFactory; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* @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, | ||||||||||||||||
ItemsFactory $itemsFactory, | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New argument to constructor may only be added as the last on the list to preserve backwards compatibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By last, you mean last one that does not have a default value, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, @ishakhsuvarov means last in a list of all existing arguments (after all existing arguments). |
||||||||||||||||
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, | ||||||||||||||||
array $searchModules = [] | ||||||||||||||||
array $searchModules = [], | ||||||||||||||||
array $previewModules = [] | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Such changes to the class constructor violate backwards compatibility policy:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like
And then loading the optional constructor if null via |
||||||||||||||||
) { | ||||||||||||||||
$this->itemsFactory = $itemsFactory; | ||||||||||||||||
$this->_searchModules = $searchModules; | ||||||||||||||||
$this->previewModules = $previewModules; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||||||||||||||||
} | ||||||||||||||||
|
@@ -52,7 +74,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'), | ||||||||||||||||
|
@@ -61,34 +87,73 @@ 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); | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please provide a clear description for this function |
||||||||||||||||
* retrieve links to certain entities in the global search | ||||||||||||||||
* | ||||||||||||||||
* @return array | ||||||||||||||||
*/ | ||||||||||||||||
private function getPreviewItems() | ||||||||||||||||
{ | ||||||||||||||||
$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']) { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not correct comparison use isset instead |
||||||||||||||||
continue; | ||||||||||||||||
} | ||||||||||||||||
$result[] = [ | ||||||||||||||||
'url' => $this->getUrl($previewConfig['url']).'?search='.$query, | ||||||||||||||||
'name' => __($previewConfig['text'], $query) | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't have There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 magento2/lib/internal/Magento/Framework/Data/etc/argument/types.xsd Lines 31 to 33 in 99e85cb
Here is how this attribute interpreted magento2/lib/internal/Magento/Framework/Data/Argument/Interpreter/StringUtils.php Lines 41 to 44 in 99e85cb
But there is no any usage of this attribute in Magento. Like this one:
or this one:
|
||||||||||||||||
]; | ||||||||||||||||
} | ||||||||||||||||
return $result; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* retrieve all entity items that should appear in global search | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please, start the sentences with the capital letter |
||||||||||||||||
* | ||||||||||||||||
* @return array | ||||||||||||||||
*/ | ||||||||||||||||
private 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; | ||||||||||||||||
} | ||||||||||||||||
try { | ||||||||||||||||
$searchInstance = $this->itemsFactory->create($className); | ||||||||||||||||
$results = $searchInstance->setStart( | ||||||||||||||||
$start | ||||||||||||||||
)->setLimit( | ||||||||||||||||
$limit | ||||||||||||||||
)->setQuery( | ||||||||||||||||
$query | ||||||||||||||||
)->getResults(); | ||||||||||||||||
$items = array_merge_recursive($items, $results); | ||||||||||||||||
|
||||||||||||||||
} catch (\LogicException $exception) { | ||||||||||||||||
continue; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exception swallowing is a bad practice. if you don't know in this specific case how to handle Exception, maybe make sense not to catch it at all |
||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
return $items; | ||||||||||||||||
} | ||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
namespace Magento\Backend\Model\Search; | ||
|
||
use Magento\Backend\Api\Search\ItemsInterface; | ||
use Magento\Framework\DataObject; | ||
|
||
abstract class ItemsAbstract extends DataObject implements ItemsInterface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. introducing of abstract classes is a bad practice. In M2 we are trying to avoid using Inheritance Based API. use composition instead, if your main reason for Inheritance usage to get rid of Copy-Paste |
||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function setStart($start) | ||
{ | ||
return $this->setData(self::START, (int)$start); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function setQuery($query) | ||
{ | ||
return $this->setData(self::QUERY, $query); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function setLimit($limit) | ||
{ | ||
return $this->setData(self::LIMIT, (int)$limit); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
/** | ||
* Copyright © Magento, Inc. All rights reserved. | ||
* See COPYING.txt for license details. | ||
*/ | ||
|
||
namespace Magento\Backend\Model\Search; | ||
|
||
use Magento\Backend\Api\Search\ItemsInterface; | ||
use Magento\Framework\ObjectManagerInterface; | ||
|
||
class ItemsFactory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please consider using |
||
{ | ||
/** | ||
* @var \Magento\Framework\ObjectManagerInterface | ||
*/ | ||
private $objectManager; | ||
|
||
/** | ||
* @param \Magento\Framework\ObjectManagerInterface $objectManager | ||
*/ | ||
public function __construct(ObjectManagerInterface $objectManager) | ||
{ | ||
$this->objectManager = $objectManager; | ||
} | ||
|
||
/** | ||
* Create new search items provider instance | ||
* | ||
* @param $instanceName | ||
* @param array $data | ||
* @return ItemsInterface | ||
*/ | ||
public function create($instanceName, array $data = []) | ||
{ | ||
$object = $this->objectManager->create($instanceName, $data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like it's better to use 'get' instead of 'create' as this should be stateless object |
||
if ($object instanceof ItemsInterface) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's better to make a type check before the object instantiation. |
||
return $object; | ||
} | ||
throw new \LogicException( | ||
"The class '{$instanceName}' does not implement ".ItemsInterface::class | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use spaces between concatenation |
||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please provide description for this interface.
All interfaces marked as @ api should be accompanied with clear and precise description