Console commands and cron
Some work is not tied to opening a page: cleaning old records, exporting orders, recalculating statistics. Such work is written as a console command and run on a schedule. PrestaShop commands are ordinary Symfony commands, and a module can add its own to the common bin/console list.
Built-in commands
php bin/console list prestashop| Command | What it does |
|---|---|
prestashop:module install name | Module actions: install, uninstall, reset, enable, disable, upgrade |
cache:clear | Clears the Symfony cache |
debug:router | The list of back office routes |
debug:container | The list of services |
prestashop:theme:enable name | Enables a theme |
Where a command lives
Module commands are kept in src/Command. The Composer autoload from the lesson on services is required. Create the file by hand:
modules/acmehello/
├── config/
│ └── services.yml
└── src/
├── Command/
│ └── CleanReviewsCommand.php
└── Repository/
└── ReviewRepository.phpThe command class
<?php
// modules/acmehello/src/Command/CleanReviewsCommand.php
namespace Acme\Hello\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CleanReviewsCommand extends Command
{
protected function configure(): void
{
$this->setName('acmehello:reviews:clean');
$this->setDescription('Deletes reviews of products that no longer exist.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('The command works.');
return 0;
}
}configure() sets the command name and description, execute() does the work. The method returns an exit code: 0 is success, any other number is an error.
Registration
A command is declared as a service with the console.command tag:
# modules/acmehello/config/services.yml
services:
acme.hello.command.clean_reviews:
class: Acme\Hello\Command\CleanReviewsCommand
tags:
- { name: console.command }Clear the cache and run it:
php bin/console cache:clear
php bin/console acmehello:reviews:cleanThe console prints "The command works.". The command is available while the module is installed and enabled.
Working with data
Add a method to the repository that deletes reviews of missing products:
// modules/acmehello/src/Repository/ReviewRepository.php
public function deleteOrphans(): int
{
return (int) $this->connection->executeStatement(
'DELETE r FROM ' . $this->prefix . 'acmehello_review r
LEFT JOIN ' . $this->prefix . 'product p ON p.id_product = r.id_product
WHERE p.id_product IS NULL'
);
}The method returns the number of deleted rows. The repository is passed to the command through the constructor:
// modules/acmehello/src/Command/CleanReviewsCommand.php
private ReviewRepository $repository;
public function __construct(ReviewRepository $repository)
{
parent::__construct();
$this->repository = $repository;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$deleted = $this->repository->deleteOrphans();
$output->writeln('Reviews deleted: ' . $deleted);
return 0;
}Add use Acme\Hello\Repository\ReviewRepository; at the top of the file and an argument to the service description:
arguments:
- '@acme.hello.review_repository'Now the command prints "Reviews deleted: 0" or another number.
Running on a schedule
The schedule is managed not by PrestaShop but by the server's scheduler — cron. A line is added with the crontab -e command:
0 3 * * * php /var/www/shop/bin/console acmehello:reviews:clean >> /var/www/shop/var/logs/acmehello-cron.log 2>&1The five fields at the start are the minute, hour, day of month, month and day of week. This line runs the command every day at 3:00 and appends its output to a log file.
When there is no console
Simple hosting may give no access to cron with commands but can call an address on a schedule. Then the task is written as a front controller that does the work and checks a secret parameter:
// modules/acmehello/controllers/front/cron.php
if (!hash_equals((string) Configuration::get('ACMEHELLO_CRON_TOKEN'), (string) Tools::getValue('token'))) {
die('Forbidden');
}The secret is created when the module is installed, and the ready address is shown on the settings page.
Common mistake
A command reads Context::getContext()->customer, cart or link and gets errors. The console has no request, customer, cart or employee — the context is only partly filled. A command must receive everything it needs as arguments and work through services and database queries, not through the context.