Learn how to Handle Long-running Requests with Pimcore Process Manager

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.

Share this blog

Don’t miss out on similar articles

Connecting the Dots: Ideas for Data Synchronization

Whether you’re running an online store, managing inventory, or handling customer relations, the synchronization of data across platforms can greatly influence efficiency and accuracy. One frequent challenge many face is how to flawlessly integrate and synchronize data between two distinct systems.

Continue Reading »

What is PIM (Product Information Management)

In today’s digitally-driven landscape, ensuring seamless communication between various systems is vital. Whether you’re running an online store, managing inventory, or handling customer relations, the synchronization of data across platforms can greatly influence efficiency and accuracy.

Continue Reading »
Book a demo