Order elements position is a common use case in any CMS or a shop back-office. You may need to order, categories, products, modules or adds. The use cases are endless. Which is why we developed a generic component to help you manage your position updates quickly and efficiently. Combined with our Grid component this will help you build entity lists more easily. This component will be divided into … components:
PositionDefinition
: it defined the basic information to compute and update the position( table, id field, position field, …)PositionUpdate
: this object contains all the atomic modifications that needs to be done on your list (symbolized by PositionModification
objects)PositionUpdateFactory
: this service allows you to build a PositionUpdate
easily base on your PositionDefinition
and basic modification dataGridPositionUpdater
: it is the main part of the component which gives you interfaces to perform position modificationsThe first thing, and nearly only thing, you need to create is you PositionDefinition
which will hold the basic structure to manage the positions in your list.
To allow our component to automatically compute position updates we need a few data:
ps_category
, ps_product
, …)id_category
, id_product
)position
, pos
, rank
, …)id_category
, id_parent
, …)You can define this PositionDefinition
manually:
<?php
use PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition;
$positionDefinition = new PositionDefinition(
'product',
'id_product',
'position',
'id_category_default'
);
Or you can define a service to avoid duplicating your code:
services:
_defaults:
public: true
prestashop.product.grid.position_definition:
class: 'PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition'
arguments:
- 'product'
- 'id_product'
- 'position'
- 'id_category_default'
The good news is that you now made the hardest part, all other computing and database queries will be managed by our component.
The only thing you have to do now is provide the updates you want to apply to your list positions. We provide a default PositionUpdateFactory
to help you build your update, it is defined as a Symfony service accessible via prestashop.core.grid.position.position_update_factory
.
<?php
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdateFactory;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdate;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionDataException;
$positionsData = [
'positions' => [
[
'rowId' => 12,
'oldPosition' => 0,
'newPosition' => 1,
],
[
'rowId' => 15,
'oldPosition' => 5,
'newPosition' => 3,
]
],
'parentId' => $categoryId,
];
/** @var PositionDefinition $positionDefinition */
$positionDefinition = $this->get('prestashop.product.grid.position_definition');
/** @var PositionUpdateFactory $positionUpdateFactory */
$positionUpdateFactory = $this->get('prestashop.core.grid.position.position_update_factory');
try {
/** @var PositionUpdate $positionUpdate */
$positionUpdate = $positionUpdateFactory->buildPositionUpdate($positionsData, $positionDefinition);
} catch (PositionDataException $e) {
//An exception is thrown if the input data doesn't respect the expected format
$errors = [$e->toArray()];
$this->flashErrors($errors);
}
The format of the input data is not random nor fixed, it actually matches the definition of our PositionUpdateFactory
which you can see in the service definition:
# In src/PrestaShopBundle/Resources/config/services/core/grid.yml
...
# Grid position updater
prestashop.core.grid.position.position_update_factory:
class: 'PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdateFactory'
arguments:
- 'positions'
- 'rowId'
- 'oldPosition'
- 'newPosition'
- 'parentId'
If you need this component to match another input format you can instanciate your own factory with the appropriate settings.
Now that you built your PositionUpdate
object all you need to do is perform the modification, to do this you can use
the GridPositionUpdater
service which id is PrestaShop\PrestaShop\Core\Grid\Position\GridPositionUpdater
<?php
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdate;
use PrestaShop\PrestaShop\Core\Grid\Position\GridPositionUpdaterInterface;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionUpdateException;
/** @var PositionUpdate $positionUpdate */
$positionUpdate = buildPositionUpdate();
/** @var GridPositionUpdaterInterface $updater */
$updater = $this->get('prestashop.core.grid.position.doctrine_grid_position_updater');
try {
$updater->update($positionUpdate);
$this->clearModuleCache();
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
} catch (PositionUpdateException $e) {
$errors = [$e->toArray()];
$this->flashErrors($errors);
}
This is a example to sum up what you just learnt, here is a simple controller used in a grid in the ps_linklist module.
# Route definition for the controller
admin_link_block_update_positions:
path: /link-widget/update-positions/{hookId}
methods: [POST]
defaults:
_controller: 'PrestaShop\Module\LinkList\Controller\Admin\Improve\Design\LinkBlockController::updatePositionsAction'
_legacy_controller: AdminLinkWidget
requirements:
hookId: \d+
<?php
namespace PrestaShop\Module\LinkList\Controller\Admin\Improve\Design;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionDataException;
use PrestaShop\PrestaShop\Core\Grid\Position\Exception\PositionUpdateException;
use PrestaShop\PrestaShop\Core\Grid\Position\GridPositionUpdaterInterface;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdateFactory;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionDefinition;
use PrestaShop\PrestaShop\Core\Grid\Position\PositionUpdate;
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
use PrestaShopBundle\Security\Annotation\AdminSecurity;
use PrestaShopBundle\Security\Annotation\ModuleActivated;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class LinkBlockController.
*
* @ModuleActivated(moduleName="ps_linklist", redirectRoute="admin_module_manage")
*/
class LinkBlockController extends FrameworkBundleAdminController {
/**
* @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="Access denied.")
*
* @param Request $request
* @param int $hookId
*
* @throws \Exception
*
* @return RedirectResponse
*/
public function updatePositionsAction(Request $request, $hookId)
{
$positionsData = [
'positions' => $request->request->get('positions', null),
'parentId' => $hookId,
];
/** @var PositionDefinition $positionDefinition */
$positionDefinition = $this->get('prestashop.module.link_block.grid.position_definition');
/** @var PositionUpdateFactory $positionUpdateFactory */
$positionUpdateFactory = $this->get('prestashop.core.grid.position.position_update_factory');
try {
/** @var PositionUpdate $positionUpdate */
$positionUpdate = $positionUpdateFactory->buildPositionUpdate($positionsData, $positionDefinition);
} catch (PositionDataException $e) {
$errors = [$e->toArray()];
$this->flashErrors($errors);
return $this->redirectToRoute('admin_link_block_list');
}
/** @var GridPositionUpdaterInterface $updater */
$updater = $this->get('prestashop.core.grid.position.doctrine_grid_position_updater');
try {
$updater->update($positionUpdate);
$this->clearModuleCache();
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
} catch (PositionUpdateException $e) {
$errors = [$e->toArray()];
$this->flashErrors($errors);
}
return $this->redirectToRoute('admin_link_block_list');
}
}