├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Formatter │ ├── Comment.php │ ├── Cron.php │ ├── Header.php │ ├── InvalidEmail.php │ ├── Job.php │ ├── Output.php │ └── Time.php └── Updater │ ├── CommandCronManipulator.php │ ├── CronManipulator.php │ ├── CronUpdater.php │ ├── FileSystem.php │ ├── ProcessRunner.php │ ├── StandardFileSystem.php │ ├── SymfonyFileSystem.php │ └── SymfonyProcessRunner.php └── tests ├── Formatter └── CronTest.php └── Updater └── CronUpdaterTest.php /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | run: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | include: 11 | - php-versions: 8.0 12 | symfony-versions: 4.4.* 13 | - php-versions: 8.0 14 | symfony-versions: 5.4.* 15 | - php-versions: 8.0 16 | symfony-versions: 6.0.* 17 | - php-versions: 8.1 18 | symfony-versions: 4.4.* 19 | - php-versions: 8.1 20 | symfony-versions: 5.4.* 21 | - php-versions: 8.1 22 | symfony-versions: 6.2.* 23 | - php-versions: 8.2 24 | symfony-versions: 6.2.* 25 | 26 | name: PHP ${{ matrix.php-versions }} Test with Symfony ${{ matrix.symfony-versions }} 27 | steps: 28 | # —— Setup Github actions 🐙 ————————————————————————————————————————————— 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | 32 | # https://github.com/shivammathur/setup-php (community) 33 | - name: Setup PHP, with composer and extensions 34 | uses: shivammathur/setup-php@v2 35 | with: 36 | php-version: ${{ matrix.php-versions }} 37 | extensions: mbstring, xml, ctype, iconv, intl 38 | coverage: xdebug #optional 39 | 40 | # https://github.com/marketplace/actions/setup-php-action#problem-matchers 41 | - name: Setup problem matchers for PHP 42 | run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" 43 | 44 | # https://github.com/marketplace/actions/setup-php-action#problem-matchers 45 | - name: Setup problem matchers for PHPUnit 46 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 47 | 48 | # —— Composer 🧙‍️ ————————————————————————————————————————————————————————— 49 | - name: Get composer cache directory 50 | id: composer-cache 51 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 52 | 53 | - name: Cache composer dependencies 54 | uses: actions/cache@v3 55 | with: 56 | path: ${{ steps.composer-cache.outputs.dir }} 57 | # Use composer.json for key, if composer.lock is not committed. 58 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 59 | key: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ matrix.symfony-versions }}-${{ hashFiles('**/composer.json') }} 60 | restore-keys: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ matrix.symfony-versions }}- 61 | 62 | - name: Validate Composer.json 63 | run: composer validate 64 | 65 | - name: Fix symfony version for symfony/process 66 | run: composer require --no-update symfony/process:"${{ matrix.symfony-versions }}"; 67 | 68 | - name: Install Composer dependencies 69 | run: composer update --no-progress --no-suggest --prefer-dist --optimize-autoloader 70 | 71 | ## —— Test ✅ ——————————————————————————————————————————————————————————— 72 | - name: Run Tests 73 | run: php bin/phpunit --coverage-text 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | .phpunit.result.cache 4 | bin/ 5 | vendor/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2020 MyBuilder Limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cronos 2 | 3 | Easily configure cron through PHP. 4 | 5 | If you use Symfony 4/5/6, you could use our [cool bundle](https://github.com/mybuilder/cronos-bundle) in order to configure your app jobs through fancy annotations! 6 | 7 | ## Setup and Configuration 8 | 9 | Require the library via composer: 10 | 11 | composer require mybuilder/cronos 12 | 13 | ## Usage 14 | 15 | ### Build Cron 16 | 17 | ```php 18 | header() 25 | ->setPath('path') 26 | ->setHome('home') 27 | ->setMailto('test@example.com') 28 | ->setShell('shell') 29 | ->setContentType('text') 30 | ->setContentTransferEncoding('utf8') 31 | ->end() 32 | ->comment('Comment') 33 | ->job('/bin/bash command --env=dev') 34 | ->setMinute(1) 35 | ->setHour(2) 36 | ->setDayOfMonth(3) 37 | ->setMonth(4) 38 | ->setDayOfWeek(5) 39 | ->setStandardOutFile('log') 40 | ->appendStandardErrorToFile('error') 41 | ->end(); 42 | 43 | echo $cron->format(); 44 | ``` 45 | 46 | That will print 47 | 48 | MAILTO=test@example.com 49 | HOME=home 50 | SHELL=shell 51 | LOGNAME=logName 52 | CONTENT_TYPE=text 53 | CONTENT_TRANSFER_ENCODING=utf8 54 | 55 | #Comment 56 | 1 2 3 4 5 /bin/bash command --env=dev > log 2>> error 57 | 58 | ### Updating Cron 59 | 60 | ```php 61 | replaceWith($cron); 73 | ``` 74 | 75 | ## Troubleshooting 76 | 77 | * The current user must have a existing crontab file to use the updater, use `crontab -e` to create one. 78 | * When a cron line is executed it is executed with the user that owns the crontab, but it will not execute any of the users default shell files so all paths etc need to be specified in the command called from the cron line. 79 | * Your crontab will not be executed if you do not have usable shell in `/etc/passwd` 80 | * If your jobs don't seem to be running, check the cron daemon is running, also check your username is in `/etc/cron.allow` and not in `/etc/cron.deny`. 81 | * Environmental substitutions do not work, you cannot use things like `$PATH`, `$HOME`, or `~/sbin`. 82 | * You cannot use `%` in the command, if you need to use it, escape the command in backticks. 83 | 84 | --- 85 | 86 | Created by [MyBuilder](http://www.mybuilder.com/) - Check out our [blog](http://tech.mybuilder.com/) for more insight into this and other open-source projects we release. 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mybuilder/cronos", 3 | "description": "Configure Cron task through PHP", 4 | "keywords": ["php", "cron"], 5 | "minimum-stability": "stable", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Gavin Love", 10 | "homepage": "https://www.mybuilder.com" 11 | }, 12 | { 13 | "name": "Keyvan Akbary", 14 | "homepage": "https://www.mybuilder.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=8.0.2", 19 | "symfony/process": "^4.4|^5.4|^6.0" 20 | }, 21 | "suggest": { 22 | "symfony/filesystem": "Allows using Symfony Filesystem" 23 | }, 24 | "require-dev": { 25 | "symfony/filesystem": "^4.4|^5.4|^6.0", 26 | "phpunit/phpunit": "^9.5" 27 | }, 28 | "autoload": { 29 | "psr-4": { "MyBuilder\\Cronos\\": "src" } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { "MyBuilder\\Cronos\\Tests\\": "tests/" } 33 | }, 34 | "config": { 35 | "bin-dir": "bin" 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "3.0-dev" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./src 17 | 18 | 19 | ./tests 20 | ./vendor 21 | 22 | 23 | 24 | 25 | tests 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Formatter/Comment.php: -------------------------------------------------------------------------------- 1 | removeLineBreaks($this->comment); 13 | } 14 | 15 | private function removeLineBreaks($text): string 16 | { 17 | return \str_replace(["\r", "\r\n", "\n", \PHP_EOL], '', $text); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Formatter/Cron.php: -------------------------------------------------------------------------------- 1 | header = new Header($this); 15 | 16 | return $this->header; 17 | } 18 | 19 | public function job(string $command): Job 20 | { 21 | $line = new Job($command, $this); 22 | $this->lines[] = $line; 23 | 24 | return $line; 25 | } 26 | 27 | public function comment(string $comment): Cron 28 | { 29 | $this->lines[] = new Comment($comment); 30 | 31 | return $this; 32 | } 33 | 34 | public function countLines(): int 35 | { 36 | return count($this->lines); 37 | } 38 | 39 | public function format(): string 40 | { 41 | $lines = ''; 42 | 43 | foreach ($this->lines as $line) { 44 | $lines .= $line->format() . \PHP_EOL; 45 | } 46 | 47 | return (($this->hasHeader()) ? $this->header->format() . \PHP_EOL : '') . \trim($lines) . \PHP_EOL; 48 | } 49 | 50 | public function hasHeader(): bool 51 | { 52 | return $this->header !== null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Formatter/Header.php: -------------------------------------------------------------------------------- 1 | path = $path; 24 | 25 | return $this; 26 | } 27 | 28 | public function setHome(string $home): self 29 | { 30 | $this->home = $home; 31 | 32 | return $this; 33 | } 34 | 35 | /** @throws InvalidEmail if given email is invalid */ 36 | public function setMailto(string $email): self 37 | { 38 | $this->assertValidEmail($email); 39 | $this->mailTo = $email; 40 | 41 | return $this; 42 | } 43 | 44 | private function assertValidEmail($email): void 45 | { 46 | if (false === \filter_var($email, \FILTER_VALIDATE_EMAIL)) { 47 | throw new InvalidEmail($email); 48 | } 49 | } 50 | 51 | /** 52 | * Set the shell to be used when executing commands 53 | * 54 | * Default is /bin/sh but can also be changed to /bin/php 55 | */ 56 | public function setShell(string $shell): self 57 | { 58 | $this->shell = $shell; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Set the content-type to use for cron output emails. 65 | */ 66 | public function setContentType(string $contentType): self 67 | { 68 | $this->contentType = $contentType; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Set the charset to use for cron output emails. 75 | */ 76 | public function setContentTransferEncoding(string $encoding): self 77 | { 78 | $this->encoding = $encoding; 79 | 80 | return $this; 81 | } 82 | 83 | public function setTimezone(string $timezone): self 84 | { 85 | $this->timezone = $timezone; 86 | 87 | return $this; 88 | } 89 | 90 | public function format(): string 91 | { 92 | $headers = ''; 93 | 94 | if ($this->path) { 95 | $headers .= $this->createHeader('PATH', $this->path); 96 | } 97 | if ($this->mailTo) { 98 | $headers .= $this->createHeader('MAILTO', $this->mailTo); 99 | } 100 | if ($this->home) { 101 | $headers .= $this->createHeader('HOME', $this->home); 102 | } 103 | if ($this->shell) { 104 | $headers .= $this->createHeader('SHELL', $this->shell); 105 | } 106 | if ($this->contentType) { 107 | $headers .= $this->createHeader('CONTENT_TYPE', $this->contentType); 108 | } 109 | if ($this->encoding) { 110 | $headers .= $this->createHeader('CONTENT_TRANSFER_ENCODING', $this->encoding); 111 | } 112 | if ($this->timezone) { 113 | $headers .= $this->createHeader('CRON_TZ', $this->timezone); 114 | } 115 | 116 | return $headers; 117 | } 118 | 119 | private function createHeader($name, $value): string 120 | { 121 | return $name . '=' . $value . \PHP_EOL; 122 | } 123 | 124 | public function end(): Cron 125 | { 126 | return $this->cron; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Formatter/InvalidEmail.php: -------------------------------------------------------------------------------- 1 | time = new Time; 13 | $this->output = new Output; 14 | } 15 | 16 | /** @see Time::setMinute */ 17 | public function setMinute(string $value): self 18 | { 19 | $this->time->setMinute($value); 20 | 21 | return $this; 22 | } 23 | 24 | /** @see Time::setHour */ 25 | public function setHour(string $value): self 26 | { 27 | $this->time->setHour($value); 28 | 29 | return $this; 30 | } 31 | 32 | /** @see Time::setDayOfMonth */ 33 | public function setDayOfMonth(string $value): self 34 | { 35 | $this->time->setDayOfMonth($value); 36 | 37 | return $this; 38 | } 39 | 40 | /** @see Time::setMonth */ 41 | public function setMonth(string $value): self 42 | { 43 | $this->time->setMonth($value); 44 | 45 | return $this; 46 | } 47 | 48 | /** @see Time::setDayOfWeek */ 49 | public function setDayOfWeek(string$value): self 50 | { 51 | $this->time->setDayOfWeek($value); 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Suppress the output of this command when executed 58 | * 59 | * @see Output::suppressOutput 60 | */ 61 | public function suppressOutput(): self 62 | { 63 | $this->output->suppressOutput(); 64 | 65 | return $this; 66 | } 67 | 68 | /** @see Output::setStandardOutFile */ 69 | public function setStandardOutFile(string $filePath): self 70 | { 71 | $this->output->setStandardOutFile($filePath); 72 | 73 | return $this; 74 | } 75 | 76 | /** @see Output::appendStandardOutToFile */ 77 | public function appendStandardOutToFile(string $filePath): self 78 | { 79 | $this->output->appendStandardOutToFile($filePath); 80 | 81 | return $this; 82 | } 83 | 84 | /** @see Output::setStandardErrorFile */ 85 | public function setStandardErrorFile(string $filePath): self 86 | { 87 | $this->output->setStandardErrorFile($filePath); 88 | 89 | return $this; 90 | } 91 | 92 | /** @see Output::appendStandardErrorToFile */ 93 | public function appendStandardErrorToFile(string $filePath): self 94 | { 95 | $this->output->appendStandardErrorToFile($filePath); 96 | 97 | return $this; 98 | } 99 | 100 | public function end(): Cron 101 | { 102 | return $this->cron; 103 | } 104 | 105 | public function format(): string 106 | { 107 | return 108 | $this->time->format() . 109 | $this->command . 110 | $this->output->format() . 111 | \PHP_EOL; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Formatter/Output.php: -------------------------------------------------------------------------------- 1 | noOutput = true; 17 | 18 | return $this; 19 | } 20 | 21 | public function setStandardOutFile(string $filePath): self 22 | { 23 | $this->stdOutFile = $filePath; 24 | $this->stdOutAppend = false; 25 | 26 | return $this; 27 | } 28 | 29 | public function appendStandardOutToFile(string $filePath): self 30 | { 31 | $this->stdOutFile = $filePath; 32 | $this->stdOutAppend = true; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * @param bool $append Either append or rewrite log file 39 | */ 40 | public function setStandardErrorFile(string $filePath, bool $append = false): self 41 | { 42 | $this->stdErrFile = $filePath; 43 | $this->stdErrAppend = $append; 44 | 45 | return $this; 46 | } 47 | 48 | public function appendStandardErrorToFile(string $filePath): self 49 | { 50 | $this->stdErrFile = $filePath; 51 | $this->stdErrAppend = true; 52 | 53 | return $this; 54 | } 55 | 56 | public function format(): string 57 | { 58 | if ($this->noOutput) { 59 | return $this->redirectStandardOutTo(self::NO_FILE) . $this->redirectStandardErrorTo(self::NO_FILE); 60 | } 61 | 62 | return $this->createOutput(); 63 | } 64 | 65 | private function redirectStandardOutTo($filePath): string 66 | { 67 | return $this->redirectOutputTo('', $this->stdOutAppend, $filePath); 68 | } 69 | 70 | private function redirectOutputTo($out, $isAppend, $filePath): string 71 | { 72 | $operator = $isAppend ? '>>' : '>'; 73 | 74 | return ' ' . $out . $operator . ' ' . $filePath; 75 | } 76 | 77 | private function redirectStandardErrorTo($filePath): string 78 | { 79 | return $this->redirectOutputTo('2', $this->stdErrAppend, $filePath); 80 | } 81 | 82 | private function createOutput(): string 83 | { 84 | $out = ''; 85 | 86 | if ($this->stdOutFile) { 87 | $out .= $this->redirectStandardOutTo($this->stdOutFile); 88 | } 89 | 90 | if ($this->stdErrFile) { 91 | $out .= $this->redirectStandardErrorTo($this->stdErrFile); 92 | } 93 | 94 | return $out; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Formatter/Time.php: -------------------------------------------------------------------------------- 1 | minute = $this->parse($value); 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @param string $value 0-23 or a list or range 47 | */ 48 | public function setHour(string $value): self 49 | { 50 | $this->hour = $this->parse($value); 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * @param string $value 0-31 or a list or range 57 | */ 58 | public function setDayOfMonth(string $value): self 59 | { 60 | $this->dayOfMonth = $this->parse($value); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param string $value 0-12 or a list or range 67 | */ 68 | public function setMonth(string $value): self 69 | { 70 | $this->month = $this->parse($value); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * @param string $value 0-7 (0 or 7 is Sun, or use names) or a list or range 77 | */ 78 | public function setDayOfWeek(string $value): self 79 | { 80 | $this->dayOfWeek = $this->parse($value); 81 | 82 | return $this; 83 | } 84 | 85 | public function format(): string 86 | { 87 | return \sprintf(self::FORMAT, $this->minute, $this->hour, $this->dayOfMonth, $this->month, $this->dayOfWeek); 88 | } 89 | 90 | private function parse(string $value): string 91 | { 92 | if (\str_starts_with($value, '/')) { 93 | return self::WILDCARD_TIME . $value; 94 | } 95 | 96 | return $value; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Updater/CommandCronManipulator.php: -------------------------------------------------------------------------------- 1 | fileSystem->createTempFile('cron', $contents); 15 | $this->processRunner->run([$this->cronCommand, $filePath]); 16 | $this->fileSystem->removeFile($filePath); 17 | } 18 | 19 | public function getContent(): string 20 | { 21 | return $this->processRunner->run([$this->cronCommand, '-l']); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Updater/CronManipulator.php: -------------------------------------------------------------------------------- 1 | cronManipulator->replace($cron->format()); 23 | } 24 | 25 | public function updateWith(Cron $cron, string $key): void 26 | { 27 | $this->cronManipulator->replace($this->updateContent($cron, $key)); 28 | } 29 | 30 | private function updateContent(Cron $cron, string $key): string 31 | { 32 | $content = $this->cronManipulator->getContent(); 33 | 34 | $count = 0; 35 | $pattern = '/\r?\n' . $this->beginKey($key) . '.*?' . self::KEY_END . '/s'; 36 | $replacedContent = \preg_replace($pattern, $this->wrapInKey($cron, $key), $content, -1, $count); 37 | 38 | if ($count > 0) { 39 | return $replacedContent; 40 | } 41 | 42 | return $this->appendContent($cron, $key, $content); 43 | } 44 | 45 | private function wrapInKey(Cron $cron, string $key): string 46 | { 47 | return \PHP_EOL . $this->beginKey($key) . \PHP_EOL . \trim($cron->format()) . \PHP_EOL . self::KEY_END; 48 | } 49 | 50 | private function beginKey(string $key): string 51 | { 52 | return \str_replace('%key%', $key, self::KEY_BEGIN); 53 | } 54 | 55 | private function appendContent(Cron $cron, string $key, string $content): string 56 | { 57 | return $content . $this->wrapInKey($cron, $key) . \PHP_EOL; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Updater/FileSystem.php: -------------------------------------------------------------------------------- 1 | createTempName($prefix); 10 | \file_put_contents($filePath, $content); 11 | 12 | return $filePath; 13 | } 14 | 15 | private function createTempName(string $prefix): string 16 | { 17 | return \tempnam(\sys_get_temp_dir(), $prefix); 18 | } 19 | 20 | public function removeFile(string $filePath): void 21 | { 22 | \unlink($filePath); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Updater/SymfonyFileSystem.php: -------------------------------------------------------------------------------- 1 | filesystem = new FileSystemHelper(); 14 | } 15 | 16 | /** @throws \Symfony\Component\Filesystem\Exception\IOException If the file cannot be written to. */ 17 | public function createTempFile($prefix, $content): string 18 | { 19 | $filePath = $this->createTempName($prefix); 20 | $this->dumpToFile($filePath, $content); 21 | 22 | return $filePath; 23 | } 24 | 25 | private function dumpToFile(string $filePath, string $content): void 26 | { 27 | if (\method_exists($this->filesystem, 'dumpFile')) { 28 | $this->filesystem->dumpFile($filePath, $content); 29 | } else { 30 | \file_put_contents($filePath, $content); 31 | } 32 | } 33 | 34 | private function createTempName(string $prefix): string 35 | { 36 | return \tempnam(\sys_get_temp_dir(), $prefix); 37 | } 38 | 39 | /** @throws \Symfony\Component\Filesystem\Exception\IOException When removal fails */ 40 | public function removeFile(string $filePath): void 41 | { 42 | $this->filesystem->remove($filePath); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Updater/SymfonyProcessRunner.php: -------------------------------------------------------------------------------- 1 | run(); 13 | 14 | if (false === $process->isSuccessful()) { 15 | throw new \RuntimeException($process->getErrorOutput()); 16 | } 17 | 18 | return $process->getOutput(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Formatter/CronTest.php: -------------------------------------------------------------------------------- 1 | cron = new Cron; 15 | } 16 | 17 | /** 18 | * @test 19 | */ 20 | public function shouldBuildConfiguration(): void 21 | { 22 | $this->cron 23 | ->header() 24 | ->setPath('path') 25 | ->setHome('home') 26 | ->setMailto('test@example.com') 27 | ->setShell('shell') 28 | ->setContentType('text') 29 | ->setContentTransferEncoding('utf8') 30 | ->setTimezone('Europe/Paris') 31 | ->end() 32 | ->comment('This is a command!') 33 | ->job('/bin/bash command --env=dev') 34 | ->setMinute(1) 35 | ->setHour(2) 36 | ->setDayOfMonth(3) 37 | ->setMonth(4) 38 | ->setDayOfWeek(5) 39 | ->setStandardOutFile('log') 40 | ->appendStandardErrorToFile('error') 41 | ->end() 42 | ->comment('This is another command!') 43 | ->job('/bin/php command2 --env=prod') 44 | ->setMinute('/5') 45 | ->setDayOfWeek('sun') 46 | ->suppressOutput() 47 | ->end(); 48 | 49 | $expected = << log 2>> error 60 | 61 | #This is another command! 62 | */5 * * * sun /bin/php command2 --env=prod > /dev/null 2> /dev/null 63 | 64 | EXP; 65 | 66 | $this->assertEquals($expected, $this->cron->format()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Updater/CronUpdaterTest.php: -------------------------------------------------------------------------------- 1 | manipulatorStub = new CronManipulatorStub(); 18 | 19 | $this->updater = new CronUpdater($this->manipulatorStub); 20 | } 21 | 22 | /** 23 | * @test 24 | */ 25 | public function shouldReplaceContent(): void 26 | { 27 | $this->updater->replaceWith(new Cron()); 28 | 29 | $this->assertEquals(\PHP_EOL, $this->manipulatorStub->contents); 30 | } 31 | 32 | /** 33 | * @test 34 | */ 35 | public function shouldAppendKeyIfNotExist(): void 36 | { 37 | $this->manipulatorStub->contents = <<header()->setPath('path')->end(); 47 | $cron->comment('new content'); 48 | $this->updater->updateWith($cron, 'key2'); 49 | 50 | $expectedCron = <<assertEquals($expectedCron, $this->manipulatorStub->contents); 65 | } 66 | 67 | /** 68 | * @test 69 | */ 70 | public function shouldReplaceKeyIfExist(): void 71 | { 72 | $this->manipulatorStub->contents = <<comment('replace'); 89 | $this->updater->updateWith($cron, 'key2'); 90 | 91 | $expectedCron = <<assertEquals($expectedCron, $this->manipulatorStub->contents); 107 | } 108 | } 109 | 110 | class CronManipulatorStub implements CronManipulator 111 | { 112 | public string $contents; 113 | 114 | public function replace(string $contents): void 115 | { 116 | $this->contents = $contents; 117 | } 118 | 119 | public function getContent(): string 120 | { 121 | return $this->contents; 122 | } 123 | } 124 | --------------------------------------------------------------------------------