├── .travis.yml ├── Command └── CronRunCommand.php ├── Cron ├── Manager.php └── Task.php ├── DependencyInjection ├── Configuration.php └── SbkCronExtension.php ├── LICENSE ├── README.md ├── Resources └── config │ └── services.yml ├── SbkCronBundle.php ├── Tests ├── Cron │ ├── ManagerTest.php │ └── TaskTest.php ├── DependencyInjection │ └── SbkCronExtensionTest.php └── bootstrap.php ├── composer.json └── phpunit.xml.dist /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | env: 9 | - SYMFONY_VERSION=2.1.* 10 | - SYMFONY_VERSION=2.2.* 11 | - SYMFONY_VERSION=2.3.* 12 | - SYMFONY_VERSION=dev-master 13 | 14 | before_script: 15 | - composer require symfony/framework-bundle:${SYMFONY_VERSION} --no-update 16 | - composer update --dev 17 | 18 | script: phpunit --coverage-text 19 | 20 | notifications: 21 | email: 22 | - seb.kueck@gmail.com 23 | 24 | matrix: 25 | allow_failures: 26 | - env: SYMFONY_VERSION=dev-master 27 | -------------------------------------------------------------------------------- /Command/CronRunCommand.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | namespace Sbk\Bundle\CronBundle\Command; 9 | 10 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 11 | use Symfony\Component\Console\Input\InputInterface; 12 | use Symfony\Component\Console\Output\OutputInterface; 13 | 14 | /** 15 | * Class CronRunCommand 16 | * @package Sbk\Bundle\CronBundle\Command 17 | */ 18 | class CronRunCommand extends ContainerAwareCommand 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | protected function configure() 24 | { 25 | $this 26 | ->setName('cron:run') 27 | ->setDescription('Start the cron manager'); 28 | } 29 | 30 | /** 31 | * @param InputInterface $input 32 | * @param OutputInterface $output 33 | * @return int|null|void 34 | */ 35 | protected function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | $cronManager = $this->getContainer()->get('sbk_cron.manager'); 38 | $cronManager->forkTasks(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Cron/Manager.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | namespace Sbk\Bundle\CronBundle\Cron; 9 | 10 | use Psr\Log\LoggerInterface; 11 | use Symfony\Component\Process\Process; 12 | 13 | /** 14 | * Class Manager 15 | * @package Sbk\Bundle\CronBundle\Cron 16 | */ 17 | class Manager 18 | { 19 | protected $tasks = array(); 20 | protected $logger; 21 | 22 | protected $defaultScript; 23 | 24 | /** 25 | * @return \Psr\Log\LoggerInterface 26 | */ 27 | public function getLogger() 28 | { 29 | return $this->logger; 30 | } 31 | 32 | /** 33 | * @return Task[] 34 | */ 35 | public function getTasks() 36 | { 37 | return $this->tasks; 38 | } 39 | 40 | /** 41 | * @param \Psr\Log\LoggerInterface $logger 42 | * @param $defaultScript 43 | * @param $taskConfig 44 | */ 45 | public function __construct(LoggerInterface $logger, $defaultScript, array $taskConfig) 46 | { 47 | $this->defaultScript = $defaultScript; 48 | $this->logger = $logger; 49 | $this->initTasks($taskConfig); 50 | } 51 | 52 | /** 53 | * Initialize the tasks 54 | * 55 | * @param array $config 56 | */ 57 | public function initTasks(array $config) 58 | { 59 | foreach ($config as $taskName => $taskConfig) { 60 | $this->initTask($taskName, $taskConfig); 61 | } 62 | } 63 | 64 | /** 65 | * Initialize a single task 66 | * 67 | * @param $taskName 68 | * @param array $taskConfig 69 | */ 70 | protected function initTask($taskName, array $taskConfig) 71 | { 72 | if (false === isset($taskConfig['script'])) { 73 | $taskConfig['script'] = $this->defaultScript; 74 | } 75 | try { 76 | $task = new Task($taskName, $taskConfig); 77 | $this->tasks[$taskName] = $task; 78 | } catch (\Exception $e) { 79 | $this->getLogger()->error(sprintf('Could not instantiate task "%s"', $taskName)); 80 | } 81 | } 82 | 83 | /** 84 | * Starts forking the valid tasks 85 | */ 86 | public function forkTasks() 87 | { 88 | foreach ($this->getTasks() as $task) { 89 | $this->forkTask($task); 90 | } 91 | } 92 | 93 | /** 94 | * Create a child process for a Task 95 | * 96 | * @param Task $task 97 | */ 98 | public function forkTask(Task $task) 99 | { 100 | $cronExpression = $task->getCronExpression(); 101 | if (true === $cronExpression->isDue()) { 102 | $this->getLogger()->info( 103 | sprintf('Starting cron task "%s"', $task->getName()), 104 | array( 105 | 'command' => $task->getCommandToExecute() 106 | ) 107 | ); 108 | $taskProcess = new Process($task->getCommandToExecute()); 109 | $taskProcess->start(); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /Cron/Task.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | namespace Sbk\Bundle\CronBundle\Cron; 9 | 10 | use Cron\CronExpression; 11 | use Symfony\Component\OptionsResolver\OptionsResolver; 12 | use Symfony\Component\OptionsResolver\OptionsResolverInterface; 13 | 14 | class Task 15 | { 16 | protected $name; 17 | protected $bin; 18 | protected $script; 19 | protected $command; 20 | protected $cronExpression; 21 | 22 | /** 23 | * @param mixed $command 24 | */ 25 | public function setCommand($command) 26 | { 27 | $this->command = $command; 28 | } 29 | 30 | /** 31 | * @return mixed 32 | */ 33 | public function getCommand() 34 | { 35 | return $this->command; 36 | } 37 | 38 | /** 39 | * @param mixed $name 40 | */ 41 | public function setName($name) 42 | { 43 | $this->name = $name; 44 | } 45 | 46 | /** 47 | * @return mixed 48 | */ 49 | public function getName() 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * @param mixed $bin 56 | */ 57 | public function setBin($bin) 58 | { 59 | $this->bin = $bin; 60 | } 61 | 62 | /** 63 | * @return mixed 64 | */ 65 | public function getBin() 66 | { 67 | return $this->bin; 68 | } 69 | 70 | /** 71 | * @param mixed $cronExpression 72 | */ 73 | public function setCronExpression($cronExpression) 74 | { 75 | $this->cronExpression = $cronExpression; 76 | } 77 | 78 | /** 79 | * @return mixed 80 | */ 81 | public function getCronExpression() 82 | { 83 | return $this->cronExpression; 84 | } 85 | 86 | /** 87 | * @param mixed $script 88 | */ 89 | public function setScript($script) 90 | { 91 | $this->script = $script; 92 | } 93 | 94 | /** 95 | * @return mixed 96 | */ 97 | public function getScript() 98 | { 99 | return $this->script; 100 | } 101 | 102 | /** 103 | * @param $name 104 | * @param array $config 105 | */ 106 | public function __construct($name, array $config) 107 | { 108 | $this->name = $name; 109 | $this->assignConfiguration($config); 110 | } 111 | 112 | /** 113 | * @param array $config 114 | */ 115 | protected function assignConfiguration(array $config) 116 | { 117 | $resolver = new OptionsResolver(); 118 | $this->setDefaultOptions($resolver); 119 | 120 | $options = $resolver->resolve($config); 121 | 122 | $this->bin = $options['bin']; 123 | $this->script = $options['script']; 124 | $this->command = $options['command']; 125 | 126 | $cronExpression = CronExpression::factory($options['expression']); 127 | $this->cronExpression = $cronExpression; 128 | } 129 | 130 | /** 131 | * @param OptionsResolverInterface $resolver 132 | */ 133 | protected function setDefaultOptions(OptionsResolverInterface $resolver) 134 | { 135 | $resolver->setDefaults( 136 | array( 137 | 'bin' => 'php', 138 | 'script' => null, 139 | 'command' => null, 140 | 'expression' => '', 141 | ) 142 | ); 143 | } 144 | 145 | /** 146 | * Returns the executable command for the task instance 147 | * 148 | * @return string 149 | */ 150 | public function getCommandToExecute() 151 | { 152 | $command = implode( 153 | " ", 154 | array( 155 | $this->bin, 156 | $this->script, 157 | $this->command, 158 | ) 159 | ); 160 | return trim(preg_replace('/\s+/', ' ', $command)); 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('sbk_cron'); 21 | 22 | $rootNode 23 | ->children() 24 | ->arrayNode('tasks') 25 | ->prototype('array') 26 | ->children() 27 | ->scalarNode('bin')->defaultValue('php')->end() 28 | ->scalarNode('script')->end() 29 | ->scalarNode('command')->end() 30 | ->scalarNode('expression')->isRequired()->end() 31 | ->end() 32 | ->end() 33 | ->end() 34 | ->end(); 35 | 36 | return $treeBuilder; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DependencyInjection/SbkCronExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 24 | 25 | $container->setParameter('sbk_cron.tasks', $config['tasks']); 26 | 27 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 28 | $loader->load('services.yml'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sebastian Kück 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SbkCronBundle 2 | ============= 3 | 4 | Symfony2 Bundle for setting up cron jobs via configuration. 5 | 6 | [![Build Status](https://travis-ci.org/skck/SbkCronBundle.png?branch=master)](https://travis-ci.org/skck/SbkCronBundle) 7 | 8 | # Installation 9 | 10 | ## Prerequisites 11 | 12 | This bundle requires Symfony 2.1+, as it must be installed with composer. 13 | 14 | ## Download SbkCronBundle with composer 15 | 16 | Add the following to your project `composer.json`: 17 | 18 | ```js 19 | { 20 | "require": { 21 | "sbk/cron-bundle": "dev-master" 22 | } 23 | } 24 | ``` 25 | 26 | Now install it with this command: 27 | 28 | ```bash 29 | $ php composer.phar update sbk/cron-bundle 30 | ``` 31 | 32 | The bundle should be downloaded to the `vendor` directory. 33 | 34 | ## Enable the bundle 35 | 36 | You need to add the bundle in `app/AppKernel.php` 37 | 38 | ```php 39 | /var/log/listoutput.log" 79 | expression: "@daily" 80 | 81 | ``` 82 | 83 | Each entry in `sbk_cron.tasks` represents a task. 84 | 85 | **`command`** 86 | 87 | The command to execute. By default the cron manager will prepend `php %kernel.root_dir%/console` before the command name, so configuring console commands is easy. You can run every command you want with this bundle, read on how to do so. 88 | 89 | **`expression`** 90 | 91 | The cron expression, any valid expression that you would enter in a cron table. 92 | 93 | **`bin`** 94 | 95 | The binary which will execute the command (`php` by default, you could enter `''` to omit the bin in the execution command) 96 | 97 | **`script`** 98 | 99 | The script that will be called (`%kernel.root_dir%/console` by default). 100 | 101 | ## Runnign the master cron job 102 | 103 | Even if you can configure all cron jobs with this bundle, you need to add one line to the crontab manually, the `cron:run` command. 104 | 105 | ``` 106 | * * * * * php /var/www/app/console cron:run 107 | ``` 108 | 109 | This will execute the cron manager every minute. 110 | 111 | The manager will check which tasks need to be executed and will create background processes for every due task. 112 | 113 | -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | sbk_cron.manager.class: Sbk\Bundle\CronBundle\Cron\Manager 3 | 4 | services: 5 | sbk_cron.manager: 6 | class: "%sbk_cron.manager.class%" 7 | arguments: ["@logger", "%kernel.root_dir%/console", "%sbk_cron.tasks%"] 8 | tags: 9 | - { name: monolog.logger, channel: sbk_cron } 10 | -------------------------------------------------------------------------------- /SbkCronBundle.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | namespace Sbk\Bundle\CronBundle\Tests\Cron; 9 | 10 | use Monolog\Logger; 11 | use Sbk\Bundle\CronBundle\Cron\Manager; 12 | 13 | class ManagerTest extends \PHPUnit_Framework_TestCase 14 | { 15 | protected $loggerStub; 16 | 17 | protected function setUp() 18 | { 19 | $this->loggerStub = $this->getMockBuilder('Monolog\Logger') 20 | ->setConstructorArgs(array('phpunit')) 21 | ->getMock(); 22 | } 23 | 24 | 25 | public function testInstance() 26 | { 27 | $manager = new Manager($this->loggerStub, null, array()); 28 | $this->assertInstanceOf('Sbk\Bundle\CronBundle\Cron\Manager', $manager); 29 | } 30 | 31 | public function testTasks() 32 | { 33 | $tasksConfig = array( 34 | 'testtask' => array( 35 | 'bin' => 'ls', 36 | 'command' => '-l', 37 | 'expression' => '* * * * *', 38 | ), 39 | 'falsetask' => array( 40 | 'bin' => 'ls', 41 | 'command' => '-l', 42 | 'expression' => 'invalid expression' 43 | ) 44 | ); 45 | $manager = new Manager($this->loggerStub, null, $tasksConfig); 46 | $tasks = $manager->getTasks(); 47 | $this->assertCount(1, $tasks); 48 | 49 | foreach ($tasks as $task) { 50 | $this->assertInstanceOf('Sbk\Bundle\CronBundle\Cron\Task', $task); 51 | } 52 | } 53 | 54 | public function testForkTasks() 55 | { 56 | $tasksConfig = array( 57 | 'testtask' => array( 58 | 'bin' => 'ls', 59 | 'command' => '-l', 60 | 'expression' => '* * * * *', 61 | ), 62 | ); 63 | $manager = new Manager($this->loggerStub, null, $tasksConfig); 64 | $manager->forkTasks(); 65 | } 66 | } -------------------------------------------------------------------------------- /Tests/Cron/TaskTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | namespace Sbk\Bundle\CronBundle\Tests\Cron; 10 | 11 | use Sbk\Bundle\CronBundle\Cron\Task; 12 | 13 | class TaskTest extends \PHPUnit_Framework_TestCase 14 | { 15 | protected $taskOptions = array( 16 | 'bin' => 'ls', 17 | 'command' => '-l', 18 | 'expression' => '* * * * *', 19 | ); 20 | 21 | public function testInstance() 22 | { 23 | $task = new Task('unittest', $this->taskOptions); 24 | $this->assertEquals('unittest', $task->getName()); 25 | } 26 | 27 | public function testCommandToExecute() 28 | { 29 | $task = new Task('unittest', $this->taskOptions); 30 | $commandToExecute = $task->getCommandToExecute(); 31 | $this->assertEquals('ls -l', $commandToExecute); 32 | } 33 | 34 | public function testCronExpression() 35 | { 36 | $task = new Task('unittest', $this->taskOptions); 37 | $this->assertInstanceOf('Cron\CronExpression', $task->getCronExpression()); 38 | } 39 | } -------------------------------------------------------------------------------- /Tests/DependencyInjection/SbkCronExtensionTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | namespace Sbk\Bundle\CronBundle\Tests; 10 | 11 | 12 | use Sbk\Bundle\CronBundle\DependencyInjection\SbkCronExtension; 13 | use Symfony\Component\DependencyInjection\ContainerBuilder; 14 | 15 | class SbkCronExtensionTest extends \PHPUnit_Framework_TestCase 16 | { 17 | public function testExtension() 18 | { 19 | $loader = new SbkCronExtension(); 20 | $config = array(); 21 | $loader->load(array($config), new ContainerBuilder()); 22 | } 23 | 24 | /** 25 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 26 | */ 27 | public function testInvalidConfigException() 28 | { 29 | $loader = new SbkCronExtension(); 30 | $config = array('foo'=>'bar'); 31 | $loader->load(array($config), new ContainerBuilder()); 32 | } 33 | 34 | public function testConfiguration() 35 | { 36 | $loader = new SbkCronExtension(); 37 | $config = array( 38 | 'tasks' => array( 39 | 'testtask' => array( 40 | 'bin' => 'php', 41 | 'script' => '', 42 | 'command' => '-i', 43 | 'expression' => '* * * * *' 44 | ) 45 | ) 46 | ); 47 | $loader->load(array($config), new ContainerBuilder()); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.3.2", 16 | "symfony/framework-bundle": "~2.1", 17 | "mtdowling/cron-expression": "1.0.*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~4.3", 21 | "symfony/options-resolver": "~2.1", 22 | "symfony/process": "~2.1", 23 | "symfony/console": "~2.1", 24 | "monolog/monolog": "~1.7.0" 25 | }, 26 | "autoload": { 27 | "psr-0": { "Sbk\\Bundle\\CronBundle": "" } 28 | }, 29 | "target-dir": "Sbk/Bundle/CronBundle" 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ./Tests 8 | 9 | 10 | 11 | 12 | 13 | ./ 14 | 15 | ./Tests 16 | ./vendor 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------