PHPackages                             precision-soft/symfony-console - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [CLI &amp; Console](/categories/cli)
4. /
5. precision-soft/symfony-console

ActiveSymfony-bundle[CLI &amp; Console](/categories/cli)

precision-soft/symfony-console
==============================

Symfony bundle for generating cron, Supervisor, and Kubernetes configuration files

v4.2.11(2w ago)08.3k↓51.5%2MITPHPPHP &gt;=8.2

Since Sep 19Pushed 2w agoCompare

[ Source](https://github.com/precision-soft/symfony-console)[ Packagist](https://packagist.org/packages/precision-soft/symfony-console)[ Docs](https://github.com/precision-soft/symfony-console)[ RSS](/packages/precision-soft-symfony-console/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (10)Dependencies (32)Versions (39)Used By (2)

Symfony Console
===============

[](#symfony-console)

[![PHP >= 8.2](https://camo.githubusercontent.com/8f0af9c5395ae4ef8ba7a7ad65fa61c44927ea9c3eb3be91a13c678254f29bd4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d383839324246)](https://www.php.net/)[![PHPStan Level 8](https://camo.githubusercontent.com/44dc5f71fec76653887c975fe3db546a82ff603d094798eb6414a38369db1f44/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230382d627269676874677265656e)](https://phpstan.org/)[![Code Style PER-CS2.0](https://camo.githubusercontent.com/5cbab3b5c635536159b4d0a5ef49ebc70fcc20757f82d5f83bc251d872914301/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64652532307374796c652d5045522d2d4353322e302d626c7565)](https://www.php-fig.org/per/coding-style/)[![License MIT](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

A Symfony bundle for generating configuration files for cron jobs and workers. It supports multiple output templates including crontab, Supervisor, and Kubernetes (CronJob and Worker) formats.

**You may fork and modify it as you wish**.

Any suggestions are welcomed.

Features
--------

[](#features)

- Generate crontab configuration files from Symfony bundle config
- Generate Supervisor worker configuration files
- Generate Kubernetes CronJob and Worker manifests
- Automatic heartbeat command injection for cron jobs
- Memory and time limit traits for long-running commands
- Instance-aware commands for parallel execution

Requirements
------------

[](#requirements)

- PHP 8.2+
- Symfony 7

Installation
------------

[](#installation)

```
composer require precision-soft/symfony-console
```

Commands
--------

[](#commands)

CommandDescription`precision-soft:symfony:console:cronjob-create`Generates cron job configuration files based on the bundle config`precision-soft:symfony:console:worker-create`Generates worker configuration files based on the bundle configConfiguration
-------------

[](#configuration)

### Cron job configuration

[](#cron-job-configuration)

**precision\_soft\_symfony\_console.yaml**

```
precision_soft_symfony_console:
    cronjob:
        config:
            template_class: PrecisionSoft\Symfony\Console\Template\CrontabTemplate
            conf_files_dir: '%kernel.project_dir%/generated_conf/cron'
            logs_dir: '%kernel.logs_dir%/cron'
            settings:
                log: true
                destination_file: 'crontab'
                heartbeat: true
        commands:
            list:
                command: '%kernel.project_dir%/bin/console list'
                user: 'www-data'
                log_file_name: 'list.log'
                destination_file: 'custom-crontab'
                schedule:
                    minute: '*'
                    hour: '*'
                    day_of_month: '*'
                    month: '*'
                    day_of_week: '*'
                settings:
                    log: false
```

If **precision\_soft\_symfony\_console.cronjob.config.settings.heartbeat** is set to `true`, a heartbeat command will automatically be added to each generated crontab file. The auto-generated heartbeat command runs `/bin/touch /heartbeat.` every minute. You may override the heartbeat by defining a command named `heartbeat` in the commands list.

The **user** setting at config level prepends the user to each crontab command line. It can be overridden per command via the command-level `user` option. Each command also supports `log_file_name` (custom log file name, defaults to `.log`) and `destination_file` (override the config-level destination file to generate separate crontab files per command).

### Worker configuration (Supervisor)

[](#worker-configuration-supervisor)

```
precision_soft_symfony_console:
    worker:
        config:
            template_class: PrecisionSoft\Symfony\Console\Template\SupervisorTemplate
            conf_files_dir: '%kernel.project_dir%/generated_conf/worker'
            logs_dir: '%kernel.logs_dir%/worker'
            settings:
                number_of_processes: 1
                auto_start: true
                auto_restart: true
                prefix: 'app-name'
                user: 'root'
        commands:
            messenger-consume:
                command: '%kernel.project_dir%/bin/console messenger:consume async'
                settings:
                    number_of_processes: 2
```

Each command generates a separate `.conf` file for Supervisor. The `prefix`, `user`, `auto_start`, `auto_restart`, `log_file`, and `number_of_processes` are available settings with defaults (can be set at the config level and overridden per command). If `log_file` is not specified, it defaults to `/.log`.

### Kubernetes CronJob template

[](#kubernetes-cronjob-template)

```
precision_soft_symfony_console:
    cronjob:
        config:
            template_class: PrecisionSoft\Symfony\Console\Template\KubernetesCronjobTemplate
            conf_files_dir: '%kernel.project_dir%/generated_conf/k8s-cron'
            logs_dir: '%kernel.logs_dir%/cron'
            settings:
                destination_file: 'cronjobs.yaml'
        commands:
            cleanup:
                command: '%kernel.project_dir%/bin/console app:cleanup'
                schedule:
                    minute: '0'
                    hour: '3'
                    day_of_month: '*'
                    month: '*'
                    day_of_week: '*'
```

### Kubernetes Worker template

[](#kubernetes-worker-template)

```
precision_soft_symfony_console:
    worker:
        config:
            template_class: PrecisionSoft\Symfony\Console\Template\KubernetesWorkerTemplate
            conf_files_dir: '%kernel.project_dir%/generated_conf/k8s-worker'
            logs_dir: '%kernel.logs_dir%/worker'
            settings:
                destination_file: 'workers.yaml'
                number_of_processes: 1
        commands:
            messenger-consume:
                command: '%kernel.project_dir%/bin/console messenger:consume async'
                settings:
                    number_of_processes: 3
```

The `destination_file` setting is mandatory for both Kubernetes templates. The Kubernetes Worker template has no default. The Kubernetes CronJob template defaults to `crontab` from the cronjob config settings if not overridden per command.

Available templates
-------------------

[](#available-templates)

Template classOutput format`CrontabTemplate`Standard crontab file`SupervisorTemplate`Supervisor `.conf` files (one per command)`KubernetesCronjobTemplate`Kubernetes CronJob manifest`KubernetesWorkerTemplate`Kubernetes Worker manifestCommand traits
--------------

[](#command-traits)

The bundle provides traits for long-running Symfony commands.

### MemoryLimitTrait

[](#memorylimittrait)

Adds a `--memory-limit` option and monitors memory usage during execution.

```
use PrecisionSoft\Symfony\Console\Command\AbstractCommand;
use PrecisionSoft\Symfony\Console\Command\Trait\MemoryLimitTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends AbstractCommand
{
    use MemoryLimitTrait;

    protected function configure(): void
    {
        $this->configureMemoryLimit('512M');
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);
        $this->initializeMemoryLimit();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        foreach ($this->getItems() as $item) {
            $this->processItem($item);

            if (true === $this->getMemoryLimitReached()) {
                break;
            }
        }

        return self::SUCCESS;
    }
}
```

### TimeLimitTrait

[](#timelimittrait)

Adds a `--time-limit` option (seconds) to stop after a given runtime.

```
use PrecisionSoft\Symfony\Console\Command\AbstractCommand;
use PrecisionSoft\Symfony\Console\Command\Trait\TimeLimitTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends AbstractCommand
{
    use TimeLimitTrait;

    protected function configure(): void
    {
        $this->configureTimeLimit(600);
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);
        $this->initializeTimeLimit();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        foreach ($this->getItems() as $item) {
            $this->processItem($item);

            if (true === $this->getTimeLimitReached()) {
                break;
            }
        }

        return self::SUCCESS;
    }
}
```

### MemoryAndTimeLimitsTrait

[](#memoryandtimelimitstrait)

Combines both limits into one trait. Calls `stopScriptIfLimitsReached()` which throws `LimitExceededException` when either limit is exceeded — catch it to perform cleanup before exiting.

```
use PrecisionSoft\Symfony\Console\Command\AbstractCommand;
use PrecisionSoft\Symfony\Console\Command\Trait\MemoryAndTimeLimitsTrait;
use PrecisionSoft\Symfony\Console\Exception\LimitExceededException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends AbstractCommand
{
    use MemoryAndTimeLimitsTrait;

    protected function configure(): void
    {
        $this->configureMemoryAndTimeLimits('512M', 600);
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);
        $this->initializeMemoryAndTimeLimits();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            foreach ($this->getItems() as $item) {
                $this->stopScriptIfLimitsReached();
                $this->processItem($item);
            }
        } catch (LimitExceededException $limitExceededException) {
            $this->warning($limitExceededException->getMessage());
        }

        return self::SUCCESS;
    }
}
```

### InstancesTrait

[](#instancestrait)

Adds `--max-instances` and `--instance-index` options for parallel execution of the same command.

```
use PrecisionSoft\Symfony\Console\Command\AbstractCommand;
use PrecisionSoft\Symfony\Console\Command\Trait\InstancesTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends AbstractCommand
{
    use InstancesTrait;

    protected function configure(): void
    {
        $this->configureInstances();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        [$maxInstances, $instanceIndex] = $this->computeInstances();

        $this->writeln($this->formatMessageWithInstances('processing'));
        return self::SUCCESS;
    }
}
```

Contracts
---------

[](#contracts)

The bundle defines the following interfaces in the `PrecisionSoft\Symfony\Console\Contract` namespace:

InterfacePurpose`TemplateInterface`Implemented by all templates — `generate(ConfigInterface, array): ConfFilesDto``ConfigInterface`Provides template class, logs dir, conf files dir, and settings`SettingsInterface`Provides access to the settings object via `getSettings(): SettingInterface``SettingInterface`Retrieves a single setting value via `getSetting(string): ?string` — boolean values are returned as the literal strings `'true'` / `'false'`, `null` stays `null`, other scalars are cast to stringServices
--------

[](#services)

### MemoryService

[](#memoryservice)

Static utility for memory operations (`PrecisionSoft\Symfony\Console\Service\MemoryService`):

MethodDescription`setMemoryLimitIfNotHigher(string $newLimit): void`Raises `memory_limit` only if the new limit is higher than the current one`getMemoryUsage(): string`Returns current memory usage in human-readable format`convertBytesToHumanReadable(int $bytes): string`Converts bytes to human-readable string (e.g. `128 MB`)`returnBytes(string $value): int`Parses a memory string (`512M`, `1G`) into bytes### AttributeService

[](#attributeservice)

Static utility for command metadata (`PrecisionSoft\Symfony\Console\Service\AttributeService`):

MethodDescription`getCommandName(string $commandClass): string`Extracts the command name from the `AsCommand` attribute of a command classExceptions
----------

[](#exceptions)

All exceptions extend `PrecisionSoft\Symfony\Console\Exception\Exception`:

ExceptionThrown when`ConfGenerateException`Configuration file generation or write fails`InvalidConfigurationException`Required configuration is missing or invalid`InvalidValueException`A value (e.g. memory limit) cannot be parsed`LimitExceededException`Memory or time limit is exceeded (`MemoryAndTimeLimitsTrait`)`SettingNotFoundException`A requested setting does not exist on the DTOAbstractCommand
---------------

[](#abstractcommand)

`PrecisionSoft\Symfony\Console\Command\AbstractCommand` extends Symfony's `Command` and provides:

- Automatic `$this->input`, `$this->output`, and `$this->style` (`SymfonyStyle`) initialization in `initialize()`
- Output helper methods via `SymfonyStyleTrait`: `writeln()`, `error()`, `info()`, `warning()`, `success()`
- The decorated title block is emitted only when the output is decorated (TTY) and verbosity is above quiet — piped / redirected / `-q` invocations stay clean for machine consumers

For custom templates
--------------------

[](#for-custom-templates)

Create a template service implementing `TemplateInterface` (`PrecisionSoft\Symfony\Console\Contract\TemplateInterface`) and add to your **services.yaml**:

```
services:
    _instanceof:
        PrecisionSoft\Symfony\Console\Contract\TemplateInterface:
            tags: [ 'precision-soft.symfony.console.template' ]
```

Troubleshooting
---------------

[](#troubleshooting)

### Memory limit trait reports incorrect usage

[](#memory-limit-trait-reports-incorrect-usage)

`MemoryLimitTrait` reads `memory_limit` from `php.ini` via `\ini_get()`. If your environment sets `-1` (unlimited), the trait returns `false` for `getMemoryLimitReached()` — this is intentional. To enforce a limit, always pass an explicit value to `configureMemoryLimit()`.

### Generated config files have wrong permissions

[](#generated-config-files-have-wrong-permissions)

`ConfFileWriter` creates files with the permissions of the running PHP process. If the generated crontab or Supervisor config needs specific ownership (e.g. `root`), adjust permissions after generation or run the command as the target user.

### Kubernetes template throws InvalidConfigurationException

[](#kubernetes-template-throws-invalidconfigurationexception)

Both `KubernetesCronjobTemplate` and `KubernetesWorkerTemplate` require the `destination_file` setting. Unlike `CrontabTemplate` (which defaults to `crontab`), Kubernetes templates have no default — set it explicitly in your config.

### Command traits conflict with existing setUp/tearDown

[](#command-traits-conflict-with-existing-setupteardown)

The command traits (`MemoryLimitTrait`, `TimeLimitTrait`) use `initialize()` hooks, not `setUp()`/`tearDown()`. They are safe to combine with any test base class. Call `initializeMemoryLimit()` or `initializeTimeLimit()` in your command's `initialize()` method.

Security Considerations
-----------------------

[](#security-considerations)

### Heartbeat files

[](#heartbeat-files)

When `heartbeat` is enabled, the crontab generator adds a `/bin/touch /heartbeat.` command that runs every minute. Ensure:

- **`logs_dir` is not web-accessible** — heartbeat files should not be reachable via HTTP
- **Directory permissions are restricted** — only the cron user and monitoring tools should have read/write access
- **Monitor heartbeat staleness** — the purpose of heartbeat files is to detect when cron stops running; alert if the file modification time exceeds your threshold (e.g. 5 minutes)

### Path traversal protection

[](#path-traversal-protection)

`ConfFileWriter` validates that all generated file paths stay within the configured `conf_files_dir`. Paths containing `..` or resolving outside the destination directory are rejected with `ConfGenerateException`. Each written file is additionally canonicalized via `realpath` and re-checked against the (also canonicalized) temporary directory, blocking symlink-based escapes that pass textual checks; the temp directory itself is verified to be a real directory (not a pre-existing symlink) to close a TOCTOU window after `mkdir`. Do not bypass these checks by symlinking the destination to a sensitive location.

### Configuration values in generated files

[](#configuration-values-in-generated-files)

Command parts (the `command` array) are rendered verbatim into generated config files (crontab, Supervisor `.conf`, Kubernetes YAML). The templates do not shell-escape command parts, so sanitizing command input (shell metacharacters, newlines) is the caller's responsibility. YAML special characters in Kubernetes templates are escaped via `escapeYamlValue()`, and log file paths in crontab are escaped via `\escapeshellarg()`. Do not pass untrusted user input as command strings or settings.

Dev
---

[](#dev)

The development environment uses Docker. The `./dc` script is a Docker Compose wrapper located in `.dev/`.

```
git clone git@github.com:precision-soft/symfony-console.git
cd symfony-console

./dc build && ./dc up -d
```

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance96

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity64

Established project with proven stability

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~17 days

Total

38

Last Release

17d ago

Major Versions

v1.2.1 → v2.0.02024-12-11

v2.3.7 → v3.0.02026-03-30

v3.0.3 → v4.0.02026-04-04

### Community

Maintainers

![](https://www.gravatar.com/avatar/dc22fddecbd2128c6ed8b90f69c5a5bc1445f3e42ea1845b03c0f26a955ae307?d=identicon)[adrian.jeledintan](/maintainers/adrian.jeledintan)

---

Tags

consolesymfonyworkercrontabsupervisorkubernetescronjob

###  Code Quality

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/precision-soft-symfony-console/health.svg)

```
[![Health](https://phpackages.com/badges/precision-soft-symfony-console/health.svg)](https://phpackages.com/packages/precision-soft-symfony-console)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
