In this tutorial we’ll show you a way to use the process manager bundle in Pimcore to handle requests from the frontend that you want to run asynchronously in the backend, separately from the frontend request.
Official process manager site: https://github.com/elements-at/ProcessManager
To understand and follow this tutorial you need to have mediocre knowledge of Pimcore and the Symfony framework.
If you need to create a bundle to integrate this solution, you can follow official documentation:
https://pimcore.com/docs/pimcore/current/Development_Documentation/Extending_Pimcore/Bundle_Developers_Guide/index.html
For the tutorial purposes I created an ExampleBundle bundle.
Install process manager bundle
First we want to add the process manager bundle to our Pimcore project.
We use composer to handle everything.
composer require elements/process-manager-bundle
After installation we need to enable and install the bundle.
bin/console pimcore:bundle:enable ElementsProcessManagerBundle
bin/console pimcore:bundle:install ElementsProcessManagerBundle
Process manager requires maintenance cron job to run in the background, so open your crontab or cron alternatives of choice and add the following line:
* * * * * php /var/www/html/bin/console process-manager:maintenance > /dev/null 2>&1
Important: your path could differ from mine – double check it!
Create a new Command
Create a new folder inside your bundle ‘Command’ → inside this folder we want to create a Symfony Command – official pimcore example.
Hint: Don’t forget to register the command in services!
services:
ExampleBundle\Command\:
resource: '../../Command'
public: true
tags: [ 'console.command' ]
bundles/ExampleBundle/Resources/config/services.yml
It is important to add ExecutionTrait – this way, Process Manager can interact with the command.
use ExecutionTrait;
I configured the command as follows:
protected function configure()
{
$this->setName('example:generate-changes')
->setDescription('Will generate changes to selected item')
->addOption(
'monitoring-item-id',
null,
InputOption::VALUE_REQUIRED,
'Contains the monitoring item if executed via the Pimcore backend'
);
}
We want to set up a command execute method – due to the tutorial flow we can implement it completely later. For now let’s just return 0 (success).
protected function execute(InputInterface $input, OutputInterface $output): int
{
return self::SUCCESS;
}
Configure a new Process Manager Command
After installing the process manager bundle, we can see a new menu item inside the administration: Tools → Process Manager
We should see a green button ‘Add Pimcore Command’.
I configured mine as follows:
- ID: 1
- Name: Generate Changes
- Command: example:generate-changes
After you have saved the configuration, you will want to save the ID somewhere inside your codebase. I can give you 2 options: thefirst is to just define a constant inside your constants.php and the other is to create a Config class inside your bundle and create a public constant, which is the way I chose.
bundles/ExampleBundle/Config/ExampleConfig.php
class ExampleConfig
{
public const GENERATE_CHANGES = 1;
}
Frontend handler
We want to simulate a handler on the frontend. For this example we’ll just add a splitbutton on a class – in my case this class is ExampleClass, but frontend requests can be sent from any solution you create.
So in my example I’ll edit and add a Pimcore function that runs after every object is opened postOpenObject inside bundles/ExampleBundle/Resources/public/js/pimcore/startup.js
postOpenObject: function (object, type) {
if (object.data.general.o_className === 'ExampleClass') {
object.toolbar.add(
{
xtype: 'splitbutton',
tooltip: 'Changes',
iconCls: 'pimcore_icon_area pimcore_material_icon',
scale: 'medium',
handler: function () {
},
menu: new Ext.menu.Menu({
items: [
{
text: 'Generate Changes', iconCls: 'pimcore_icon_area', handler: function () {
Ext.Ajax.request({
url: '/admin/example/generate-changes',
method: 'GET',
params: {
isAjax: true,
objectID: object.id,
},
success: function (response) {
console.log(response);
},
failure: function (response) {
console.log(response);
let responseObj = Ext.decode(response.responseText);
Ext.Msg.alert('Contact administrator!', responseObj.msg + ' Contact administrator.');
}
}, this);
}
}
]
})
});
pimcore.layout.refresh();
}
}
With frontend render handled, we need to implement a controller for our request.
Implementing the Controller
Inside Pimcore you have 2 different types of controllers. One is a general frontend controller and the other is an admin controller that is meant to be executed inside the administration.
We’ll use the latter in this example.
Create a new controller:
bundles/ExampleBundle/Controller/Admin/GenerateChangesController.php which extends AdminController. We want to add an action that will handle our route.
Controllers are registered in services and autowired if you used bundle generator, otherwise register it yourself.
If you use smart IDEs, it should autoimport classes that we’re using; the important one is:
use Elements\Bundle\ProcessManagerBundle\Helper;
I’m sure you can figure the rest of the imports out yourself. ☺️
class GenerateChangesController extends AdminController
{
/**
* @Route("/admin/example/generate-changes")
* @throws Exception
*/
public function generateChangesAction(Request $request): Response
{
$getParams = $request->query->all();
if (!isset($getParams['objectID'])) {
throw new Exception("Missing request parameter!");
}
$metaData = [
'objectID' => $getParams['objectID']
];
Helper::executeJob(ExampleConfig::GENERATE_CHANGES_PROCESS_ID, [], $this->getAdminUser()->getId(), $metaData);
return new Response(json_encode(["success" => true]));
}
}
A little explanation of what is going on here.
We received a request from the administration (the button we created). This request gives us information on what object ID we’re going to be working with.
Process Manager can hold information, it is called Meta Data files. Here we’re using a simplified version of this implementation.
So Process Manager Helper class offers a static function executeJob that executes a job in the background via \Pimcore\Tool\Console::execInBackground($command). This is all done for you, so all you need to do is specify which Process ID you want executing, who is executing it (you can implement restrictions/permissions) and the data that goes with it.
Finishing the Command
Now that we know what we’re going to be working with, we can implement the logic behind our request.
Normally I would write a Model to handle the work, but since this is a simple example, we’ll implement a simple logic inside the Command that changes a field value and saves the object.
bundles/ExampleBundle/Command/GenerateChangesCommand.php
protected function execute(InputInterface $input, OutputInterface $output): int
{
$monitoringItemId = $input->getOption('monitoring-item-id');
if (is_null($monitoringItemId)) {
throw new Exception('Missing Monitoring Item ID (--monitoring-item-id=?? ...)');
}
$monitoringItem = $this->initProcessManager($monitoringItemId, ["autoCreate" => true]);
$monitoringItem->setName("Generate random changes.");
$monitoringItem->setCurrentWorkload(0)->setTotalWorkload(1)->setMessage("Starting process")->save();
$stringifyMetaData = $monitoringItem->getMetaData();
$metaData = json_decode($stringifyMetaData, true);
if (!isset($metaData['objectID'])) {
return self::FAILURE;
}
try {
$exampleClass = ExampleClass::getById($metaData['objectID']);
$exampleClass->setSku(rand(100000, 999999));
$exampleClass->save();
} catch (Exception $e) {
Simple::log('generate-changes', date("h:i:sa") . $e->getMessage());
return self::FAILURE;
}
$monitoringItem->setMessage("Job finished => Success")->setCompleted();
$output->writeln("DONE process | Success");
return self::SUCCESS;
}
So what is happening here?
Process manager always sends an option monitoring-item-id. We need to retrieve information from the monitoring item that we created with the controller.
This is done with the ExecutionTrait ‘magic’ method initProcessManager. Through this we get an object where we can configure everything, the name of the process item, how big the workload is, messages and logs… One thing that we need is the object ID. If you remember, we saved it inside Meta Data, so we’re retrieving it from there.
Metadata is saved as a json string so we need to decode it using json_decode. Afterwards you can do whatever you need to do with the information.
In my case I got an object and set a random value to a field.
After your logic is completed, you must change the status of the process item to completed.
Additional tips
Some tips for an easier workflow.
You can check logs inside the Tools → Process Manager → Process Logs window.
After you have created a new process, you can check the details of the process. Inside the details window we can see the command that the process is executing. An example:
/usr/local/bin/php /var/www/html/bin/console example:generate-changes --monitoring-item-id=10
We can use this information to debug it with XDebug step by step, which is the best way (personal opinion) to develop a feature.
From upper information I would run a following command in my console:
XDEBUG_CONFIG=idekey=PHPSTORM PHP_IDE_CONFIG=serverName=pim.localhost bin/console example:generate-changes --monitoring-item-id=10
Of course you need to have a server configured correctly inside your IDE for this to work properly and Xdebug enabled inside your php configuration.
Summary
We created a controller that takes a request from the administration. The controller creates a new process that we have configured inside the administration. The process manager executes the new process according to the configuration. In our case we have created a command that implements ExecutionTrait: We used Metadata to transfer information between the controller and the command.