├── .gitignore ├── Tests ├── Fixtures │ ├── App │ │ ├── app │ │ │ └── config │ │ │ │ ├── routing.yml │ │ │ │ ├── config_validate_rest.yml │ │ │ │ ├── config_bernard.yml │ │ │ │ ├── services.yml │ │ │ │ └── config_test.yml │ │ └── Bundle │ │ │ └── TestBundle │ │ │ ├── TestBundle.php │ │ │ ├── DependencyInjection │ │ │ ├── Configuration.php │ │ │ └── TestExtension.php │ │ │ └── Entity │ │ │ └── Entity.php │ ├── Job │ │ ├── TestResponse.php │ │ ├── ProcessControl │ │ │ └── DoStopController.php │ │ ├── LoggerAwareJob.php │ │ ├── ControllerAwareJob.php │ │ ├── JobAwareJob.php │ │ └── ManagerAwareJob.php │ └── Annotation │ │ └── AnnotatedJob.php ├── bootstrap.php ├── Functional │ ├── Adapter │ │ └── BernardTest.php │ ├── Controller │ │ └── JobTypeControllerTest.php │ ├── Job │ │ ├── Mailer │ │ │ └── MailerTest.php │ │ └── ConnectionTest.php │ └── Doctrine │ │ └── LogManagerTest.php ├── Validator │ ├── Job │ │ └── AbstractConstraintProviderTest.php │ └── Constraints │ │ └── JobTest.php ├── Job │ ├── JobTypeNotFoundExceptionTest.php │ ├── Exception │ │ └── TicketNotFoundExceptionTest.php │ ├── ScheduleBuilderTest.php │ ├── Queue │ │ └── QueueConfigTest.php │ ├── StatusTest.php │ └── ExceptionResponseTest.php ├── Event │ ├── TerminationEventTest.php │ └── ExecutionEventTest.php ├── Model │ ├── LogTest.php │ ├── ScheduleTest.php │ └── JobTest.php ├── Entity │ ├── ScheduleTest.php │ └── JobTest.php ├── Locker │ └── NullLockerTest.php ├── Adapter │ ├── Sonata │ │ └── Fixtures │ │ │ └── TestIterator.php │ └── Bernard │ │ └── ConsumerAdapterTest.php └── Logger │ └── Handler │ ├── StreamHandlerFactoryTest.php │ ├── OrmHandlerFactoryTest.php │ └── BaseHandlerFactoryTest.php ├── Resources ├── views │ ├── Demo │ │ └── index.html.twig │ └── layout.html.twig ├── docs │ ├── todo.md │ ├── rest-api.md │ ├── clustered-environment.md │ ├── scheduled-jobs.md │ ├── process-control.md │ ├── message-consuming.md │ ├── cancel-jobs.md │ ├── serialization.md │ └── logging.md └── config │ ├── routing │ ├── rest-all.xml │ ├── rest-job-type.xml │ └── rest-job.xml │ ├── services │ ├── commands.xml │ ├── locker.xml │ ├── logger_stream.xml │ ├── validator.xml │ ├── scheduler.xml │ ├── logger_storage_file.xml │ ├── default_jobs.xml │ ├── logger.xml │ ├── listener.xml │ ├── logger_storage_orm.xml │ ├── serializer.xml │ ├── adapter_bernard.xml │ ├── orm.xml │ ├── registry.xml │ └── adapter_sonata.xml │ ├── doctrine-mapping │ └── Log.orm.xml │ └── doctrine │ ├── Schedule.orm.xml │ └── Job.orm.xml ├── CHANGELOG.md ├── Serializer ├── SerializationContext.php ├── DeserializationContext.php ├── SerializerInterface.php ├── Serializer.php └── EventDispatcher │ └── JobDeserializationSubscriber.php ├── Job ├── JobAwareInterface.php ├── ManagerAwareInterface.php ├── JobParameterArray.php ├── Queue │ ├── ConsumerInterface.php │ ├── ProducerInterface.php │ ├── QueueConfigInterface.php │ ├── Message.php │ └── QueueConfig.php ├── ScheduleBuilder.php ├── LogManagerInterface.php ├── ProcessControl │ └── FactoryInterface.php ├── JobTypeNotFoundException.php ├── Exception │ ├── TicketNotFoundException.php │ └── ValidationFailedException.php ├── Context │ ├── Exception │ │ └── ParameterNotFoundException.php │ ├── ContextInterface.php │ └── Context.php ├── ExceptionResponse.php ├── Parameter │ └── DefaultJobsConstraintProvider.php ├── Sleeper.php ├── JobBuilder.php └── Mailer │ └── Mailer.php ├── Model ├── AbstractListInterface.php ├── JobList.php ├── ScheduleManagerInterface.php ├── ScheduleInterface.php ├── AbstractList.php ├── JobManager.php ├── JobInterface.php ├── LogManagerInterface.php ├── LogManager.php └── LogInterface.php ├── Logger ├── Entity │ └── Log.php ├── LoggerFactoryInterface.php └── Handler │ ├── OrmHandlerFactory.php │ ├── HandlerFactoryInterface.php │ ├── JobAwareOrmHandler.php │ ├── HandlerFactoryRegistry.php │ ├── StreamHandlerFactory.php │ ├── BaseHandlerFactory.php │ └── OrmHandler.php ├── Validator ├── Constraints │ ├── Status.php │ ├── JobType.php │ ├── Parameters.php │ ├── Job.php │ ├── StatusValidator.php │ ├── JobTypeValidator.php │ └── JobValidator.php └── Job │ ├── AbstractConstraintProvider.php │ └── ConstraintProviderInterface.php ├── Annotation ├── ReturnType.php └── ParamType.php ├── Test ├── MockHelper.php ├── WebTestCase.php ├── AdapterTest.php └── DatabaseWebTestCase.php ├── Locker └── NullLocker.php ├── Event ├── TerminationEvent.php ├── ExecutionEvent.php └── JobEvents.php ├── Entity ├── DetachingJobManager.php ├── Log.php ├── Job.php ├── LogManager.php └── Schedule.php ├── LICENSE ├── DependencyInjection └── Compiler │ ├── RegisterEventListenersPass.php │ ├── ConfigurationCheckPass.php │ ├── RegisterDoctrineListenerPass.php │ ├── LockerConfigurationPass.php │ ├── ControllerConfigurationPass.php │ └── RegisterConstraintProvidersPass.php ├── Api ├── ParameterConstraintViolation.php └── BadRequestResponse.php ├── Controller ├── JobTypeController.php └── BaseController.php ├── Adapter └── Bernard │ ├── ConsumerAdapter.php │ └── ControlledConsumer.php ├── .travis.yml ├── phpunit.xml.dist ├── Doctrine └── ScheduleManager.php ├── composer.json ├── Listener ├── JobListener.php └── ScheduleListener.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | build/* 3 | composer.lock 4 | phpunit.xml 5 | /Tests/Fixtures/App/app/config/parameters.yml 6 | vendor/* -------------------------------------------------------------------------------- /Tests/Fixtures/App/app/config/routing.yml: -------------------------------------------------------------------------------- 1 | abc-rest-all: 2 | resource: "@AbcJobBundle/Resources/config/routing/rest-all.xml" 3 | prefix: /api -------------------------------------------------------------------------------- /Tests/Fixtures/App/app/config/config_validate_rest.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config_test.yml } 3 | 4 | abc_job: 5 | rest: 6 | validate: true -------------------------------------------------------------------------------- /Resources/views/Demo/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "AbcJobBundle::layout.html.twig" %} 2 | 3 | {% block title %}Job Bundle{% endblock title %} 4 | 5 | {% block content %} 6 | {{ form(form) }} 7 | {% endblock %} -------------------------------------------------------------------------------- /Resources/docs/todo.md: -------------------------------------------------------------------------------- 1 | ### Planned Features 2 | 3 | - Add support for CouchDB, MongoDB 4 | - Add support for [qpush-bundle](https://www.google.de/webhp?q=qpushbundle) 5 | - Integrate https://github.com/beberlei/metrics 6 | - Utilize stopwatch -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 2016-10-15 5 | ---------- 6 | * Removed custom Doctrine type abc.job.status 7 | * Removed dependency to AbcEnumSerializerBundle 8 | 9 | 10 | 2018-02-17 11 | ---------- 12 | * Updated PHP to >7.0 13 | * Updated unit tests -------------------------------------------------------------------------------- /Tests/Fixtures/App/app/config/config_bernard.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config_test.yml } 3 | 4 | framework: 5 | test: ~ 6 | session: 7 | storage_id: session.storage.mock_file 8 | profiler: 9 | collect: false 10 | 11 | abc_job: 12 | adapter: bernard -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% block content %} 11 | {% endblock content %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/Fixtures/App/Bundle/TestBundle/TestBundle.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\App\Bundle\TestBundle; 12 | 13 | use Symfony\Component\HttpKernel\Bundle\Bundle; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class TestBundle extends Bundle 19 | { 20 | } -------------------------------------------------------------------------------- /Serializer/SerializationContext.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Serializer; 12 | 13 | use JMS\Serializer\SerializationContext as BaseSerializationContext; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class SerializationContext extends BaseSerializationContext 19 | { 20 | } -------------------------------------------------------------------------------- /Job/JobAwareInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface JobAwareInterface 17 | { 18 | /** 19 | * @param JobInterface $job 20 | * @return void 21 | */ 22 | public function setJob(JobInterface $job); 23 | } -------------------------------------------------------------------------------- /Serializer/DeserializationContext.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Serializer; 12 | 13 | use JMS\Serializer\DeserializationContext as BaseDeserializationContext; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class DeserializationContext extends BaseDeserializationContext 19 | { 20 | } -------------------------------------------------------------------------------- /Resources/config/routing/rest-job-type.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | AbcJobBundle:JobType:list 9 | json 10 | json 11 | 12 | 13 | -------------------------------------------------------------------------------- /Job/ManagerAwareInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface ManagerAwareInterface 17 | { 18 | /** 19 | * @param ManagerInterface $manager 20 | * @return void 21 | */ 22 | public function setManager(ManagerInterface $manager); 23 | } -------------------------------------------------------------------------------- /Model/AbstractListInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface AbstractListInterface 9 | { 10 | /** 11 | * @return \ArrayAccess 12 | */ 13 | public function getItems(); 14 | 15 | /** 16 | * @param \ArrayAccess $items 17 | */ 18 | public function setItems($items); 19 | 20 | /** 21 | * @return int 22 | */ 23 | public function getTotalCount(); 24 | 25 | /** 26 | * @param int $totalCount 27 | */ 28 | public function setTotalCount($totalCount); 29 | } -------------------------------------------------------------------------------- /Logger/Entity/Log.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Entity\Log as BaseLog; 14 | 15 | /** 16 | * Only reason this class is defined here is on order to register the mapping (Doctrine, MongodDB, CouchDB) optionally 17 | * 18 | * @author Hannes Schulz 19 | */ 20 | class Log extends BaseLog 21 | { 22 | } -------------------------------------------------------------------------------- /Tests/Functional/Adapter/BernardTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Functional\Adapter; 12 | 13 | use Abc\Bundle\JobBundle\Test\AdapterTest; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class BernardTest extends AdapterTest 19 | { 20 | public function getEnvironment() 21 | { 22 | return 'bernard'; 23 | } 24 | } -------------------------------------------------------------------------------- /Model/JobList.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\JobBundle\Model\AbstractList; 14 | use JMS\Serializer\Annotation\Type; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class JobList extends AbstractList 20 | { 21 | /** 22 | * @Type("array") 23 | */ 24 | protected $items; 25 | } -------------------------------------------------------------------------------- /Resources/config/services/commands.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Validator/Constraints/Status.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Symfony\Component\Validator\Constraint; 14 | 15 | /** 16 | * @Annotation 17 | * @Target({"PROPERTY"}) 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | class Status extends Constraint 22 | { 23 | public $message = 'The value "{{string}}" is not status.'; 24 | } -------------------------------------------------------------------------------- /Validator/Job/AbstractConstraintProvider.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | abstract class AbstractConstraintProvider implements ConstraintProviderInterface 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function getPriority() 22 | { 23 | return -1; 24 | } 25 | } -------------------------------------------------------------------------------- /Validator/Constraints/JobType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Symfony\Component\Validator\Constraint; 14 | 15 | /** 16 | * @Annotation 17 | * @Target({"PROPERTY"}) 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | class JobType extends Constraint 22 | { 23 | public $message = 'The value "{{string}}" is not valid job type.'; 24 | } 25 | -------------------------------------------------------------------------------- /Annotation/ReturnType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Annotation; 12 | 13 | /** 14 | * @Annotation 15 | * @Target({"METHOD"}) 16 | * @author Hannes Schulz 17 | */ 18 | class ReturnType 19 | { 20 | /** 21 | * @Required 22 | * @var string 23 | */ 24 | public $type; 25 | 26 | /** 27 | * @var array 28 | */ 29 | public $options = array(); 30 | } -------------------------------------------------------------------------------- /Validator/Constraints/Parameters.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Symfony\Component\Validator\Constraint; 14 | 15 | /** 16 | * @Annotation 17 | * @Target({"PROPERTY"}) 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | class Parameters extends Constraint 22 | { 23 | public $type; 24 | public $message = 'The value must be an array.'; 25 | } -------------------------------------------------------------------------------- /Job/JobParameterArray.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | /** 14 | * JobParameterArray 15 | * 16 | * This class for now only acts as a type identifier for the serializer in order to serialize/deserialize the parameters of a job. 17 | * 18 | * @author Hannes Schulz 19 | * @see \Abc\Bundle\JobBundle\Serializer\Handler\JobParameterArrayHandler 20 | */ 21 | class JobParameterArray 22 | { 23 | } -------------------------------------------------------------------------------- /Test/MockHelper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Test; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | class MockHelper 17 | { 18 | /** 19 | * @param $class 20 | * @return string 21 | */ 22 | public static function getNamespace($class) { 23 | $pieces = explode("\\", $class); 24 | array_pop($pieces); 25 | return implode("\\", $pieces); 26 | } 27 | } -------------------------------------------------------------------------------- /Job/Queue/ConsumerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Queue; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface ConsumerInterface 17 | { 18 | /** 19 | * Consumes messages from the queue. 20 | * 21 | * @param string $queue The name of the queue 22 | * @param array $options 23 | * @return void 24 | */ 25 | public function consume($queue, array $options = []); 26 | } -------------------------------------------------------------------------------- /Resources/docs/rest-api.md: -------------------------------------------------------------------------------- 1 | REST-API 2 | ======== 3 | 4 | The AbcJobBundle ships with a JSON REST-API. To use this you need to make sure the following bundles are installed and configured: 5 | 6 | * [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle) 7 | 8 | Next you need to make sure that the routing files are imported: 9 | 10 | ```yaml 11 | # app/config/routing.yml 12 | abc-rest-job: 13 | type: rest 14 | resource: "@AbcJobBundle/Resources/config/routing/rest-all.xml" 15 | prefix: /api 16 | ``` 17 | 18 | You can now see an overview of all available API methods using API documentation provided by the [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle). 19 | 20 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Resources/config/services/locker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | %abc.resource_lock.model.resource_lock.class% 12 | abc-job-lock 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Logger/LoggerFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Psr\Log\LoggerInterface; 15 | 16 | /** 17 | * Factory for loggers and logs for jobs. 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | interface LoggerFactoryInterface 22 | { 23 | /** 24 | * @param JobInterface $job 25 | * @return LoggerInterface 26 | */ 27 | public function create(JobInterface $job); 28 | } -------------------------------------------------------------------------------- /Tests/Validator/Job/AbstractConstraintProviderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Validator\Job; 12 | 13 | 14 | use Abc\Bundle\JobBundle\Validator\Job\AbstractConstraintProvider; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class AbstractConstraintProviderTest extends TestCase 18 | { 19 | public function testGetPriority() 20 | { 21 | $subject = $this->getMockForAbstractClass(AbstractConstraintProvider::class); 22 | $this->assertEquals(-1, $subject->getPriority()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Annotation/ParamType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Annotation; 12 | 13 | /** 14 | * @Annotation 15 | * @Target({"METHOD"}) 16 | * @author Hannes Schulz 17 | */ 18 | final class ParamType 19 | { 20 | /** 21 | * @Required 22 | * @var string 23 | */ 24 | public $name; 25 | 26 | /** 27 | * @Required 28 | * @var string 29 | */ 30 | public $type; 31 | 32 | /** 33 | * @var array 34 | */ 35 | public $options = array(); 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Job/JobTypeNotFoundExceptionTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobTypeNotFoundException; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class JobTypeNotFoundExceptionTest extends TestCase 20 | { 21 | public function testGetType() 22 | { 23 | $subject = new JobTypeNotFoundException('foobar'); 24 | $this->assertEquals('foobar', $subject->getType()); 25 | } 26 | } -------------------------------------------------------------------------------- /Job/Queue/ProducerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Queue; 12 | 13 | use Abc\Bundle\JobBundle\Job\ManagerAwareInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | interface ProducerInterface extends ManagerAwareInterface 19 | { 20 | /** 21 | * Sends a message to the queue. 22 | * 23 | * @param Message $message 24 | * @return void 25 | * @throws \RuntimeException If publishing fails 26 | */ 27 | public function produce(Message $message); 28 | } -------------------------------------------------------------------------------- /Job/ScheduleBuilder.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | use Abc\Bundle\JobBundle\Model\Schedule; 14 | use Abc\Bundle\JobBundle\Model\ScheduleInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ScheduleBuilder 20 | { 21 | /** 22 | * @param $type 23 | * @param $expression 24 | * @return ScheduleInterface 25 | */ 26 | public static function create($type, $expression) 27 | { 28 | return new Schedule($type, $expression); 29 | } 30 | } -------------------------------------------------------------------------------- /Validator/Constraints/Job.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Symfony\Component\Validator\Constraint; 14 | 15 | /** 16 | * @Annotation 17 | * @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"}) 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | class Job extends Constraint 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function getTargets() 27 | { 28 | return [self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT]; 29 | } 30 | } -------------------------------------------------------------------------------- /Job/Queue/QueueConfigInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Queue; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface QueueConfigInterface 17 | { 18 | /** 19 | * @return string The name of the default queue 20 | */ 21 | public function getDefaultQueue(); 22 | 23 | /** 24 | * Returns the name of a queue of a job. 25 | * 26 | * @param string $type The job type 27 | * @return string The queue name 28 | */ 29 | public function getQueue($type); 30 | } -------------------------------------------------------------------------------- /Tests/Event/TerminationEventTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Event; 12 | 13 | use Abc\Bundle\JobBundle\Event\TerminationEvent; 14 | use Abc\Bundle\JobBundle\Model\Job; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class TerminationEventTest extends TestCase 21 | { 22 | public function testGetJob() 23 | { 24 | $job = new Job(); 25 | $event = new TerminationEvent($job); 26 | 27 | $this->assertSame($job, $event->getJob()); 28 | } 29 | } -------------------------------------------------------------------------------- /Tests/Job/Exception/TicketNotFoundExceptionTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job\Exception; 12 | 13 | use Abc\Bundle\JobBundle\Job\Exception\TicketNotFoundException; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class TicketNotFoundExceptionTest extends TestCase 20 | { 21 | public function testGetTicket() 22 | { 23 | $exception = new TicketNotFoundException('ticket'); 24 | $this->assertEquals('ticket', $exception->getTicket()); 25 | } 26 | } -------------------------------------------------------------------------------- /Job/LogManagerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface LogManagerInterface 17 | { 18 | /** 19 | * @param JobInterface $job 20 | * @return array The log records of a job 21 | */ 22 | public function findByJob(JobInterface $job); 23 | 24 | /** 25 | * @param JobInterface $job 26 | * @return void 27 | * @throws \RuntimeException If deletion fails 28 | */ 29 | public function deleteByJob(JobInterface $job); 30 | } -------------------------------------------------------------------------------- /Resources/docs/clustered-environment.md: -------------------------------------------------------------------------------- 1 | Clustered Environment 2 | ===================== 3 | 4 | If you want to use AbcJobBundle for a setup where you have multiple job processing nodes you have to make sure, that the same job cannot be processed concurrently by different nodes. This can happen if a longer running job is configured with schedules. To ensure that this cannot happen the AbcJobBundle integrates concept of a [resource lock](https://github.com/aboutcoders/resource-lock-bundle). Whenever a message is consumed from the queue the [job manager](./job-management.md) checks if the job is currently processed by another node and skips execution if this is the case. 5 | 6 | This feature is disabled by default. In order to enable it you have to install the [AbcResourceLockBundle](https://github.com/aboutcoders/resource-lock-bundle). 7 | 8 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Tests/Job/ScheduleBuilderTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\ScheduleBuilder; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ScheduleBuilderTest extends TestCase 20 | { 21 | public function testCreate() 22 | { 23 | $schedule = ScheduleBuilder::create('cron', '* * * * *'); 24 | 25 | $this->assertEquals('cron', $schedule->getType()); 26 | $this->assertEquals('* * * * *', $schedule->getExpression()); 27 | } 28 | } -------------------------------------------------------------------------------- /Tests/Model/LogTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Model; 12 | 13 | use Abc\Bundle\JobBundle\Model\Log; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class LogTest extends TestCase 20 | { 21 | public function testGetContext() 22 | { 23 | $subject = new Log(); 24 | $this->assertTrue(is_array($subject->getContext())); 25 | } 26 | 27 | public function testGetExtra() 28 | { 29 | $subject = new Log(); 30 | $this->assertTrue(is_array($subject->getExtra())); 31 | } 32 | } -------------------------------------------------------------------------------- /Job/ProcessControl/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\ProcessControl; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobAwareInterface; 14 | use Abc\Bundle\JobBundle\Job\JobInterface; 15 | use Abc\ProcessControl\ControllerInterface; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | interface FactoryInterface 21 | { 22 | /** 23 | * Creates the controller passed to a job implementing the 24 | * 25 | * @param JobInterface $job 26 | * @return ControllerInterface 27 | * @see JobAwareInterface 28 | */ 29 | public function create(JobInterface $job); 30 | } -------------------------------------------------------------------------------- /Tests/Validator/Constraints/JobTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Validator\Constraints; 12 | 13 | use Abc\Bundle\JobBundle\Validator\Constraints\Job; 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Component\Validator\Constraint; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class JobTest extends TestCase 21 | { 22 | public function testGetTargets() 23 | { 24 | $subject = new Job(); 25 | $this->assertContains(Constraint::CLASS_CONSTRAINT, $subject->getTargets()); 26 | $this->assertContains(Constraint::PROPERTY_CONSTRAINT, $subject->getTargets()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Locker/NullLocker.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Locker; 12 | 13 | use Abc\Bundle\ResourceLockBundle\Model\LockInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class NullLocker implements LockInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function lock($name) 24 | { 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function isLocked($name, int $autoReleaseTime = 0) 31 | { 32 | return false; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function release($name) 39 | { 40 | } 41 | } -------------------------------------------------------------------------------- /Event/TerminationEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Event; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Symfony\Component\EventDispatcher\Event; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class TerminationEvent extends Event 20 | { 21 | /** @var JobInterface */ 22 | protected $job; 23 | 24 | /** 25 | * @param JobInterface $job 26 | */ 27 | function __construct(JobInterface $job) 28 | { 29 | $this->job = $job; 30 | } 31 | 32 | /** 33 | * @return JobInterface 34 | */ 35 | public function getJob() 36 | { 37 | return $this->job; 38 | } 39 | } -------------------------------------------------------------------------------- /Resources/config/services/logger_stream.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %abc.job.logger.stream.level% 11 | %abc.job.logger.stream.bubble% 12 | %abc.job.logger.stream.path% 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/config/services/validator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/Event/ExecutionEventTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Event; 12 | 13 | use Abc\Bundle\JobBundle\Event\ExecutionEvent; 14 | use Abc\Bundle\JobBundle\Job\Context\Context; 15 | use Abc\Bundle\JobBundle\Model\Job; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class ExecutionEventTest extends TerminationEventTest 21 | { 22 | public function testGetContext() 23 | { 24 | $job = new Job(); 25 | $context = new Context(); 26 | 27 | $event = new ExecutionEvent($job, $context); 28 | 29 | $this->assertSame($job, $event->getJob()); 30 | $this->assertSame($context, $event->getContext()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Job/JobTypeNotFoundException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | class JobTypeNotFoundException extends \Exception 17 | { 18 | const CODE = 404; 19 | 20 | /** @var string */ 21 | private $type; 22 | 23 | /** 24 | * @param string $type 25 | */ 26 | public function __construct($type) 27 | { 28 | parent::__construct(sprintf('Definition "%s" not found', $type), self::CODE); 29 | 30 | $this->type = $type; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getType() 37 | { 38 | return $this->type; 39 | } 40 | } -------------------------------------------------------------------------------- /Tests/Fixtures/Job/TestResponse.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job; 12 | 13 | use JMS\Serializer\Annotation\Type; 14 | 15 | class TestResponse 16 | { 17 | /** 18 | * @var string 19 | * @Type("string") 20 | */ 21 | protected $message; 22 | 23 | function __construct($message) 24 | { 25 | $this->message = $message; 26 | } 27 | 28 | /** 29 | * @param string $message 30 | */ 31 | public function setMessage($message) 32 | { 33 | $this->message = $message; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getMessage() 40 | { 41 | return $this->message; 42 | } 43 | } -------------------------------------------------------------------------------- /Tests/Fixtures/App/Bundle/TestBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\App\Bundle\TestBundle\DependencyInjection; 12 | 13 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 14 | use Symfony\Component\Config\Definition\ConfigurationInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class Configuration implements ConfigurationInterface 20 | { 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function getConfigTreeBuilder() 25 | { 26 | $treeBuilder = new TreeBuilder(); 27 | $rootNode = $treeBuilder->root('abc_test'); 28 | 29 | return $treeBuilder; 30 | } 31 | } -------------------------------------------------------------------------------- /Job/Exception/TicketNotFoundException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Exception; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | class TicketNotFoundException extends \Exception 17 | { 18 | const CODE = 404; 19 | 20 | /** @var string */ 21 | private $ticket; 22 | 23 | /** 24 | * @param string $ticket 25 | */ 26 | public function __construct($ticket) 27 | { 28 | parent::__construct(sprintf('Ticket "%s" not found', $ticket), self::CODE); 29 | 30 | $this->ticket = $ticket; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getTicket() 37 | { 38 | return $this->ticket; 39 | } 40 | } -------------------------------------------------------------------------------- /Tests/Fixtures/Job/ProcessControl/DoStopController.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job\ProcessControl; 12 | 13 | use Abc\ProcessControl\ControllerInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class DoStopController implements ControllerInterface 19 | { 20 | /** 21 | * @return true 22 | */ 23 | public function doExit() 24 | { 25 | return true; 26 | } 27 | 28 | /** 29 | * @return true 30 | */ 31 | public function doStop() 32 | { 33 | return true; 34 | } 35 | 36 | /** 37 | * @return false 38 | */ 39 | public function doPause() 40 | { 41 | return false; 42 | } 43 | } -------------------------------------------------------------------------------- /Job/Context/Exception/ParameterNotFoundException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Context\Exception; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | class ParameterNotFoundException extends \InvalidArgumentException 17 | { 18 | 19 | /** @var string */ 20 | protected $name; 21 | 22 | /** 23 | * @param string $name The parameter name 24 | */ 25 | public function __construct($name) 26 | { 27 | parent::__construct(sprintf('A parameter with the name "%s" does not exist', $name)); 28 | 29 | $this->name = $name; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getName() 36 | { 37 | return $this->name; 38 | } 39 | } -------------------------------------------------------------------------------- /Resources/config/services/scheduler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/Model/ScheduleTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Model; 12 | 13 | use Abc\Bundle\JobBundle\Model\Schedule; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ScheduleTest extends TestCase 20 | { 21 | public function testClone() 22 | { 23 | $schedule = new Schedule(); 24 | $schedule->setCreatedAt(new \DateTime); 25 | $schedule->setUpdatedAt(new \DateTime); 26 | $schedule->setScheduledAt(new \DateTime); 27 | 28 | $clone = clone $schedule; 29 | 30 | $this->assertNull($clone->getScheduledAt()); 31 | $this->assertNull($clone->getUpdatedAt()); 32 | $this->assertNull($clone->getCreatedAt()); 33 | } 34 | } -------------------------------------------------------------------------------- /Model/ScheduleManagerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\SchedulerBundle\Model\ScheduleManagerInterface as BaseScheduleManagerInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | interface ScheduleManagerInterface extends BaseScheduleManagerInterface 19 | { 20 | /** 21 | * @param string|null $type 22 | * @param string|null $expression 23 | * @param bool $active true by default 24 | * @return ScheduleInterface 25 | */ 26 | public function create($type = null, $expression = null, $active = true); 27 | 28 | /** 29 | * @param ScheduleInterface $schedule 30 | * @return void 31 | */ 32 | public function delete(ScheduleInterface $schedule); 33 | } -------------------------------------------------------------------------------- /Resources/config/services/logger_storage_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | %abc.job.logger.storage.path% 11 | json 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | %abc.job.logger.storage.path% 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Serializer/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Serializer; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface SerializerInterface 17 | { 18 | /** 19 | * @param mixed $data 20 | * @param string $format 21 | * @param SerializationContext|null $context 22 | * 23 | * @return string 24 | */ 25 | public function serialize($data, $format, SerializationContext $context = null); 26 | 27 | /** 28 | * @param string $data 29 | * @param string $type 30 | * @param string $format 31 | * @param DeserializationContext|null $context 32 | * 33 | * @return mixed 34 | */ 35 | public function deserialize($data, $type, $format, DeserializationContext $context = null); 36 | } -------------------------------------------------------------------------------- /Resources/config/services/default_jobs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Entity/DetachingJobManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Model\JobInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class DetatchingJobManager extends JobManager 19 | { 20 | /** 21 | * @var JobInterface 22 | */ 23 | private $current; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public function save(JobInterface $job, $andFlush = true) 29 | { 30 | $this->objectManager->persist($job); 31 | if ($andFlush) { 32 | $this->objectManager->flush(); 33 | if ($this->current != null && $this->current !== $job) { 34 | $this->objectManager->detach($this->current); 35 | } 36 | $this->current = $job; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Job/Exception/ValidationFailedException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Exception; 12 | 13 | use Symfony\Component\Validator\ConstraintViolationListInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class ValidationFailedException extends \RuntimeException 19 | { 20 | /** 21 | * @var ConstraintViolationListInterface 22 | */ 23 | private $list; 24 | 25 | public function __construct(ConstraintViolationListInterface $list) 26 | { 27 | parent::__construct(sprintf('Validation failed with %d error(s).', count($list))); 28 | 29 | $this->list = $list; 30 | } 31 | 32 | /** 33 | * @return ConstraintViolationListInterface 34 | */ 35 | public function getConstraintViolationList() 36 | { 37 | return $this->list; 38 | } 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Hannes Schulz 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 furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | 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 NONINFRINGEMENT. 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. -------------------------------------------------------------------------------- /Tests/Entity/ScheduleTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Entity\Schedule; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ScheduleTest extends TestCase 20 | { 21 | public function testClone() 22 | { 23 | $schedule = new Schedule(); 24 | $schedule->setCreatedAt(new \DateTime); 25 | $schedule->setUpdatedAt(new \DateTime); 26 | $schedule->setScheduledAt(new \DateTime); 27 | 28 | $ref = new \ReflectionClass($schedule); 29 | $property = $ref->getProperty('id'); 30 | $property->setAccessible(true); 31 | $property->setValue($schedule, 1); 32 | 33 | $clone = clone $schedule; 34 | 35 | $this->assertNull($clone->getId()); 36 | } 37 | } -------------------------------------------------------------------------------- /Validator/Constraints/StatusValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Symfony\Component\Validator\Constraint; 14 | use Symfony\Component\Validator\ConstraintValidator; 15 | use Abc\Bundle\JobBundle\Job\Status as JobStatus; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class StatusValidator extends ConstraintValidator 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function validate($value, Constraint $constraint) 26 | { 27 | if(null === $value) { 28 | return; 29 | } 30 | 31 | if(!in_array($value, JobStatus::values())){ 32 | $this->context->buildViolation($constraint->message) 33 | ->setParameter('{{string}}', $value) 34 | ->addViolation(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Event/ExecutionEvent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Event; 12 | 13 | use Abc\Bundle\JobBundle\Job\Context\ContextInterface; 14 | use Abc\Bundle\JobBundle\Job\JobInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ExecutionEvent extends TerminationEvent 20 | { 21 | /** 22 | * @var ContextInterface 23 | */ 24 | protected $context; 25 | 26 | /** 27 | * @param JobInterface $job 28 | * @param ContextInterface $context 29 | */ 30 | function __construct(JobInterface $job, ContextInterface $context) 31 | { 32 | parent::__construct($job); 33 | 34 | $this->context = $context; 35 | } 36 | 37 | /** 38 | * @return ContextInterface 39 | */ 40 | public function getContext() 41 | { 42 | return $this->context; 43 | } 44 | } -------------------------------------------------------------------------------- /Entity/Log.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Model\Log as BaseLog; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class Log extends BaseLog 19 | { 20 | /** 21 | * @var mixed 22 | */ 23 | protected $id; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $jobTicket; 29 | 30 | /** 31 | * @return integer 32 | */ 33 | public function getId() 34 | { 35 | return $this->id; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getJobTicket() 42 | { 43 | return $this->jobTicket; 44 | } 45 | 46 | /** 47 | * @param string $jobTicket 48 | */ 49 | public function setJobTicket($jobTicket) 50 | { 51 | $this->jobTicket = $jobTicket; 52 | } 53 | } -------------------------------------------------------------------------------- /Resources/config/services/logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | %abc.job.logger.storage.level% 13 | %abc.job.logger.storage.bubble% 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tests/Fixtures/Job/LoggerAwareJob.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job; 12 | 13 | use Psr\Log\LoggerAwareInterface; 14 | use Psr\Log\LoggerInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class LoggerAwareJob implements LoggerAwareInterface 20 | { 21 | /** 22 | * @var LoggerInterface 23 | */ 24 | private $logger; 25 | 26 | /** 27 | * @return LoggerInterface 28 | */ 29 | public function getLogger() 30 | { 31 | return $this->logger; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function setLogger(LoggerInterface $logger) 38 | { 39 | $this->logger = $logger; 40 | } 41 | 42 | /** 43 | * @return string Returns the string 'foobar' 44 | */ 45 | public function execute() 46 | { 47 | return 'foobar'; 48 | } 49 | } -------------------------------------------------------------------------------- /Tests/Entity/JobTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Entity\Job; 14 | use Abc\Bundle\JobBundle\Entity\Schedule; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class JobTest extends TestCase 21 | { 22 | public function testCreateSchedule() 23 | { 24 | $subject = new Job(); 25 | $schedule = $subject->createSchedule('foo', 'bar'); 26 | 27 | $this->assertInstanceOf(Schedule::class, $schedule); 28 | $this->assertEquals('foo', $schedule->getType()); 29 | $this->assertEquals('bar', $schedule->getExpression()); 30 | } 31 | 32 | public function testClone() 33 | { 34 | $job = new Job; 35 | $job->setTicket('ticket'); 36 | 37 | $clone = clone $job; 38 | 39 | $this->assertTrue($job !== $clone); 40 | } 41 | } -------------------------------------------------------------------------------- /Tests/Locker/NullLockerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Locker; 12 | 13 | use Abc\Bundle\JobBundle\Locker\NullLocker; 14 | use Abc\Bundle\ResourceLockBundle\Model\LockInterface; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class NullLockerTest extends TestCase 21 | { 22 | /** 23 | * @var NullLocker 24 | */ 25 | private $subject; 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function setUp() 31 | { 32 | $this->subject = new NullLocker(); 33 | } 34 | 35 | public function testNullLockerDoesNothing() 36 | { 37 | $this->assertInstanceOf(LockInterface::class, $this->subject); 38 | $this->assertFalse($this->subject->isLocked('foobar')); 39 | $this->subject->release('barfoo'); 40 | $this->subject->lock('foobar'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterEventListenersPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use \Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass as BaseRegisterListenersPass; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class RegisterEventListenersPass extends BaseRegisterListenersPass 19 | { 20 | /** 21 | * @param string $dispatcherService Service name of the event dispatcher in processed container 22 | * @param string $listenerTag Tag name used for listener 23 | * @param string $subscriberTag Tag name used for subscribers 24 | */ 25 | public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'abc.job.event_listener', $subscriberTag = 'abc.job.event_subscriber') 26 | { 27 | parent::__construct($dispatcherService, $listenerTag, $subscriberTag); 28 | } 29 | } -------------------------------------------------------------------------------- /Model/ScheduleInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface as BaseScheduleInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | interface ScheduleInterface extends BaseScheduleInterface 19 | { 20 | /** 21 | * @param boolean $isActive 22 | */ 23 | public function setIsActive($isActive); 24 | 25 | /** 26 | * @return boolean 27 | */ 28 | public function getIsActive(); 29 | 30 | /** 31 | * @param JobInterface $job 32 | */ 33 | public function setJob(JobInterface $job); 34 | 35 | /** 36 | * @return JobInterface 37 | */ 38 | public function getJob(); 39 | 40 | /** 41 | * @return \DateTime 42 | */ 43 | public function getUpdatedAt(); 44 | 45 | /** 46 | * @return \DateTime 47 | */ 48 | public function getCreatedAt(); 49 | } -------------------------------------------------------------------------------- /Tests/Functional/Controller/JobTypeControllerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Functional\Controller; 12 | 13 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class JobTypeControllerTest extends WebTestCase 19 | { 20 | public function testCgetAction() 21 | { 22 | $client = static::createClient(); 23 | 24 | $url = '/api/job-types'; 25 | 26 | $client->request( 27 | 'GET', 28 | $url, 29 | array(), 30 | array(), 31 | array('CONTENT_TYPE' => 'application/json'), 32 | null, 33 | 'json' 34 | ); 35 | 36 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 37 | 38 | $data = $client->getResponse()->getContent(); 39 | 40 | $this->assertContains('abc.mailer', $data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Job/Queue/Message.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Queue; 12 | 13 | /** 14 | * Message to be exchanged between a queue engine and the job manager 15 | * 16 | * @author Hannes Schulz 17 | */ 18 | class Message { 19 | 20 | /** 21 | * @var string 22 | */ 23 | protected $type; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $ticket; 29 | 30 | /** 31 | * @param string $type 32 | * @param string $ticket 33 | */ 34 | function __construct($type, $ticket) 35 | { 36 | $this->type = $type; 37 | $this->ticket = $ticket; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getType() 44 | { 45 | return $this->type; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getTicket() 52 | { 53 | return $this->ticket; 54 | } 55 | } -------------------------------------------------------------------------------- /Logger/Handler/OrmHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Abc\Bundle\JobBundle\Model\LogManagerInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class OrmHandlerFactory extends BaseHandlerFactory 20 | { 21 | /** 22 | * @var LogManagerInterface 23 | */ 24 | protected $manager; 25 | 26 | /** 27 | * @param LogManagerInterface $manager 28 | */ 29 | public function __construct(LogManagerInterface $manager) 30 | { 31 | $this->manager = $manager; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function createHandler(JobInterface $job, $level, $bubble) 38 | { 39 | $handler = new JobAwareOrmHandler($this->manager, $level, $bubble); 40 | $handler->setJob($job); 41 | 42 | return $this->initHandler($handler); 43 | } 44 | } -------------------------------------------------------------------------------- /Tests/Fixtures/Annotation/AnnotatedJob.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Annotation; 12 | 13 | use Abc\Bundle\JobBundle\Annotation\ParamType; 14 | use Abc\Bundle\JobBundle\Annotation\ReturnType; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class AnnotatedJob 20 | { 21 | /** 22 | * @ParamType("param", type="string") 23 | * @param $param 24 | */ 25 | public function methodWithSingleParameters($param) 26 | { 27 | } 28 | 29 | /** 30 | * @ParamType("param1", type="string", options={}) 31 | * @ParamType("param2", type="boolean", options={"groups"={"group1"}}) 32 | * @param $param1 33 | * @param $param2 34 | */ 35 | public function methodWithMultipleParameters($param1, $param2) 36 | { 37 | } 38 | 39 | /** 40 | * @ReturnType("string", options={}) 41 | */ 42 | public function methodWithResponse() 43 | { 44 | } 45 | } -------------------------------------------------------------------------------- /Tests/Fixtures/Job/ControllerAwareJob.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job; 12 | 13 | use Abc\ProcessControl\ControllerAwareInterface; 14 | use Abc\ProcessControl\ControllerInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class ControllerAwareJob implements ControllerAwareInterface 20 | { 21 | /** 22 | * @var ControllerInterface 23 | */ 24 | private $controller; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function setController(ControllerInterface $controller) 30 | { 31 | $this->controller = $controller; 32 | } 33 | 34 | /** 35 | * @return ControllerInterface 36 | */ 37 | public function getController() 38 | { 39 | return $this->controller; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function execute() 46 | { 47 | return 'foobar'; 48 | } 49 | } -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ConfigurationCheckPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | 16 | /** 17 | * Checks if dependent bundles are properly configured. 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | final class ConfigurationCheckPass implements CompilerPassInterface 22 | { 23 | public function process(ContainerBuilder $container) 24 | { 25 | // DoctrineBundle 26 | if(!$container->has('doctrine')) { 27 | throw new \RuntimeException('You need to enable the DoctrineBundle'); 28 | } 29 | 30 | // AbcSchedulerBundle 31 | if(!$container->has('abc.scheduler.scheduler')) { 32 | throw new \RuntimeException('You need to enable the AbcSchedulerBundle'); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Tests/Fixtures/App/Bundle/TestBundle/DependencyInjection/TestExtension.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\App\Bundle\TestBundle\DependencyInjection; 12 | 13 | use Symfony\Component\Config\FileLocator; 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; 16 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class TestExtension extends Extension 22 | { 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | public function load(array $configs, ContainerBuilder $container) 27 | { 28 | $configuration = new Configuration(); 29 | $config = $this->processConfiguration($configuration, $configs); 30 | 31 | /*$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 32 | $loader->load('services.xml');*/ 33 | } 34 | } -------------------------------------------------------------------------------- /Tests/Fixtures/App/app/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | abc.job.callable: 4 | class: Abc\Bundle\JobBundle\Tests\Fixtures\Job\TestJob 5 | calls: 6 | - [ setContainer, [ '@service_container' ] ] 7 | tags: 8 | - { name: abc.job, type: log, method: log } 9 | - { name: abc.job, type: throw_exception, method: throwException } 10 | - { name: abc.job, type: set_response, method: setResponse } 11 | - { name: abc.job, type: schedule, method: schedule } 12 | - { name: abc.job, type: create_schedule, method: createSchedule } 13 | - { name: abc.job, type: remove_schedule, method: removeSchedule } 14 | - { name: abc.job, type: update_schedule, method: updateSchedule } 15 | - { name: abc.job, type: manage_job, method: manageJob } 16 | - { name: abc.job, type: cancel, method: cancel } 17 | - { name: abc.job, type: parameterless, method: parameterless } 18 | - { name: abc.job, type: throw_dbal_exception, method: throwDbalException } 19 | 20 | abc.job.log.processor: 21 | class: Monolog\Processor\PsrLogMessageProcessor 22 | tags: 23 | - { name: monolog.processor, handler: abc_job } -------------------------------------------------------------------------------- /Tests/Fixtures/Job/JobAwareJob.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\Job; 14 | use Abc\Bundle\JobBundle\Job\JobAwareInterface; 15 | use Abc\Bundle\JobBundle\Job\JobInterface; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class JobAwareJob implements JobAwareInterface 21 | { 22 | /** @var JobInterface */ 23 | protected $job; 24 | 25 | public static function getMethodName() 26 | { 27 | return 'execute'; 28 | } 29 | 30 | /** 31 | * @param mixed $job 32 | */ 33 | public function setJob(JobInterface $job) 34 | { 35 | $this->job = $job; 36 | } 37 | 38 | /** 39 | * @return JobInterface 40 | */ 41 | public function getJob() 42 | { 43 | return $this->job; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function execute() 50 | { 51 | return 'foobar'; 52 | } 53 | } -------------------------------------------------------------------------------- /Api/ParameterConstraintViolation.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Api; 12 | 13 | use JMS\Serializer\Annotation\Type; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class ParameterConstraintViolation 19 | { 20 | /** 21 | * @Type("string") 22 | * @var string 23 | */ 24 | protected $name; 25 | 26 | /** 27 | * @Type("string") 28 | * @var string 29 | */ 30 | protected $message; 31 | 32 | /** 33 | * @param string $name 34 | * @param string $message 35 | */ 36 | public function __construct($name, $message) 37 | { 38 | $this->name = $name; 39 | $this->message = $message; 40 | } 41 | 42 | /** 43 | * @return string 44 | */ 45 | public function getName() 46 | { 47 | return $this->name; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getMessage() 54 | { 55 | return $this->message; 56 | } 57 | } -------------------------------------------------------------------------------- /Logger/Handler/HandlerFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Monolog\Formatter\FormatterInterface; 15 | use Monolog\Handler\HandlerInterface; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | interface HandlerFactoryInterface 21 | { 22 | /** 23 | * @param JobInterface $job 24 | * @param int $level The minimum logging level at which this handler will be triggered 25 | * @param boolean $bubble 26 | * @return HandlerInterface 27 | */ 28 | public function createHandler(JobInterface $job, $level, $bubble); 29 | 30 | /** 31 | * @param FormatterInterface $formatter 32 | * @return void 33 | */ 34 | public function setFormatter(FormatterInterface $formatter = null); 35 | 36 | /** 37 | * @param array $processors 38 | * @return void 39 | */ 40 | public function setProcessors(array $processors); 41 | } -------------------------------------------------------------------------------- /Tests/Fixtures/Job/ManagerAwareJob.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\ManagerAwareInterface; 14 | use Abc\Bundle\JobBundle\Job\ManagerInterface; 15 | use Psr\Log\LoggerInterface; 16 | use Abc\Bundle\JobBundle\Annotation\ParamType; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class ManagerAwareJob implements ManagerAwareInterface 22 | { 23 | /** 24 | * @var ManagerInterface 25 | */ 26 | private $manager; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function setManager(ManagerInterface $manager) 32 | { 33 | $this->manager = $manager; 34 | } 35 | 36 | /** 37 | * @return ManagerInterface 38 | */ 39 | public function getManager() 40 | { 41 | return $this->manager; 42 | } 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function execute() 48 | { 49 | return 'foobar'; 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/Adapter/Sonata/Fixtures/TestIterator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Adapter\Sonata\Fixtures; 12 | 13 | use Sonata\NotificationBundle\Iterator\MessageIteratorInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class TestIterator implements MessageIteratorInterface 19 | { 20 | private $position = 0; 21 | private $array; 22 | 23 | public function __construct(array $elements) 24 | { 25 | $this->position = 0; 26 | $this->array = $elements; 27 | } 28 | 29 | function rewind() 30 | { 31 | $this->position = 0; 32 | } 33 | 34 | function current() 35 | { 36 | return $this->array[$this->position]; 37 | } 38 | 39 | function key() 40 | { 41 | return $this->position; 42 | } 43 | 44 | function next() 45 | { 46 | ++$this->position; 47 | } 48 | 49 | function valid() 50 | { 51 | return isset($this->array[$this->position]); 52 | } 53 | } -------------------------------------------------------------------------------- /Logger/Handler/JobAwareOrmHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobAwareInterface; 14 | use Abc\Bundle\JobBundle\Job\JobInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class JobAwareOrmHandler extends OrmHandler implements JobAwareInterface 20 | { 21 | /** 22 | * @var JobInterface 23 | */ 24 | protected $job; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function setJob(JobInterface $job) 30 | { 31 | $this->job = $job; 32 | } 33 | 34 | /** 35 | * Sets the job ticket in $record['extra']['job_ticket'] 36 | * 37 | * {@inheritdoc} 38 | */ 39 | protected function write(array $record) 40 | { 41 | $log = $this->manager->create(); 42 | 43 | $record['extra']['job_ticket'] = $this->job->getTicket(); 44 | 45 | $this->populateLog($log, $record); 46 | 47 | $this->manager->save($log); 48 | } 49 | } -------------------------------------------------------------------------------- /Resources/docs/scheduled-jobs.md: -------------------------------------------------------------------------------- 1 | Scheduled Jobs 2 | ============== 3 | 4 | You can define one or more schedules for a job in order to configure repeated execution of a job. The bundle relies on the [AbcSchedulerBundle](https://github.org/aboutcoders/scheduler-bundle) to provide this functionality. 5 | 6 | ## Creating a job with schedules 7 | 8 | If you want to create a job with one or more schedules the recommended way is to use the `JobBuilder`: 9 | 10 | ```php 11 | use Abc\Bundle\JobBundle\Job\JobBuilder; 12 | 13 | $job = JobBuilder::create('my_job') 14 | ->addSchedule('cron', '1 * * * *') 15 | ->addSchedule('cron', '30 * * * *') 16 | ->build(); 17 | ``` 18 | 19 | ## Creating a schedule 20 | 21 | If you want to create a schedule the recommended way is to use the `ScheduleBuilder`: 22 | 23 | ```php 24 | use Abc\Bundle\JobBundle\Job\ScheduleBuilder; 25 | 26 | $schedule = ScheduleBuilder::create('cron', '1 * * * *'); 27 | ``` 28 | 29 | ## Removing a schedule 30 | 31 | You can remove previously added schedules from a job: 32 | 33 | ```php 34 | $job->removeSchedule($schedule); 35 | ``` 36 | 37 | If you want to add or remove schedules during the execution of the job please refer to the section [Managing a job at runtime](./job-management.md). 38 | 39 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Serializer/Serializer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Serializer; 12 | 13 | use JMS\Serializer\SerializerInterface as JMSSerializerInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class Serializer implements SerializerInterface 19 | { 20 | /** 21 | * @var JMSSerializerInterface 22 | */ 23 | private $serializer; 24 | 25 | public function __construct(JMSSerializerInterface $serializer) 26 | { 27 | $this->serializer = $serializer; 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function serialize($data, $format, SerializationContext $context = null) 34 | { 35 | return $this->serializer->serialize($data, $format, $context); 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function deserialize($data, $type, $format, DeserializationContext $context = null) 42 | { 43 | return $this->serializer->deserialize($data, $type, $format, $context); 44 | } 45 | } -------------------------------------------------------------------------------- /Model/AbstractList.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Wojciech Ciolko 10 | */ 11 | abstract class AbstractList implements AbstractListInterface 12 | { 13 | /** 14 | * @JMS\Type("array") 15 | * @JMS\SerializedName("items") 16 | * @var array[Entity] 17 | */ 18 | protected $items; 19 | 20 | /** 21 | * @JMS\Type("integer") 22 | * @JMS\SerializedName("totalCount") 23 | * @var int 24 | */ 25 | protected $totalCount; 26 | 27 | /** 28 | * @return mixed 29 | */ 30 | public function getItems() 31 | { 32 | return $this->items; 33 | } 34 | 35 | /** 36 | * @param mixed $items 37 | */ 38 | public function setItems($items) 39 | { 40 | $this->items = $items; 41 | } 42 | 43 | /** 44 | * @return int 45 | */ 46 | public function getTotalCount() 47 | { 48 | return $this->totalCount; 49 | } 50 | 51 | /** 52 | * @param int $totalTotalCount 53 | */ 54 | public function setTotalCount($totalTotalCount) 55 | { 56 | $this->totalCount = $totalTotalCount; 57 | } 58 | } -------------------------------------------------------------------------------- /Resources/config/services/listener.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Resources/config/services/logger_storage_orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Abc\Bundle\JobBundle\Logger\Entity\Log 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | %abc.job.model.log.class% 20 | 21 | 22 | 23 | 24 | %abc.job.model_manager_name% 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Job/ExceptionResponse.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | use JMS\Serializer\Annotation\Type; 14 | 15 | /** 16 | * Response of a job if an exception was thrown during job execution. 17 | * 18 | * @author Hannes Schulz 19 | */ 20 | class ExceptionResponse 21 | { 22 | /** 23 | * @Type("integer") 24 | * @var int 25 | */ 26 | protected $code; 27 | 28 | /** 29 | * @Type("string") 30 | * @var string 31 | */ 32 | protected $message; 33 | 34 | /** 35 | * @param \Exception $e 36 | */ 37 | function __construct(\Exception $e) 38 | { 39 | $this->code = $e->getCode(); 40 | $this->message = $e->getMessage(); 41 | } 42 | 43 | /** 44 | * @return int The exception code 45 | */ 46 | public function getCode() 47 | { 48 | return $this->code; 49 | } 50 | 51 | /** 52 | * @return string The exception message 53 | */ 54 | public function getMessage() 55 | { 56 | return $this->message; 57 | } 58 | } -------------------------------------------------------------------------------- /Tests/Job/Queue/QueueConfigTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job\Queue; 12 | 13 | use Abc\Bundle\JobBundle\Job\Queue\QueueConfig; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class QueueConfigTest extends TestCase 20 | { 21 | 22 | public function testWithDefaultConstructor() { 23 | $subject = new QueueConfig(); 24 | 25 | $this->assertEquals('default', $subject->getDefaultQueue()); 26 | $this->assertEquals('default', $subject->getQueue('foobar')); 27 | } 28 | 29 | public function testWithConfig() { 30 | $subject = new QueueConfig([ 31 | 'queueA' => ['typeA'], 32 | 'queueB' => ['typeB'] 33 | ], 'custom'); 34 | 35 | $this->assertEquals('custom', $subject->getDefaultQueue()); 36 | $this->assertEquals('queueA', $subject->getQueue('typeA')); 37 | $this->assertEquals('queueB', $subject->getQueue('typeB')); 38 | $this->assertEquals('custom', $subject->getQueue('foobar')); 39 | } 40 | } -------------------------------------------------------------------------------- /Controller/JobTypeController.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Controller; 12 | 13 | use Nelmio\ApiDocBundle\Annotation\Operation; 14 | use Nelmio\ApiDocBundle\Annotation\Model; 15 | use Swagger\Annotations as SWG; 16 | use Symfony\Component\HttpFoundation\Response; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class JobTypeController extends BaseController 22 | { 23 | /** 24 | * @Operation( 25 | * tags={"AbcJobBundle"}, 26 | * summary="Returns a collection of job types", 27 | * @SWG\Response( 28 | * response="200", 29 | * description="Returned when successful", 30 | * @SWG\Schema( 31 | * type="array", 32 | * @SWG\Items( 33 | * type="string" 34 | * ) 35 | * ) 36 | * ) 37 | * ) 38 | * @return Response 39 | */ 40 | public function listAction() 41 | { 42 | return $this->serialize($this->getRegistry()->getTypeChoices()); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /Test/WebTestCase.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Test; 12 | 13 | use Symfony\Component\HttpKernel\KernelInterface; 14 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | abstract class WebTestCase extends BaseWebTestCase 20 | { 21 | /** 22 | * Replaces services with mock instances 23 | * 24 | * @param array $services An associative array with the service id as key and the service instance as value 25 | * @see http://blog.lyrixx.info/2013/04/12/symfony2-how-to-mock-services-during-functional-tests.html 26 | */ 27 | protected function mockServices(array $services) 28 | { 29 | /** 30 | * @ignore 31 | */ 32 | static::$kernel->setKernelModifier( 33 | function (KernelInterface $kernel) use ($services) { 34 | foreach ($services as $id => $service) { 35 | $kernel->getContainer()->set($id, $service); 36 | } 37 | } 38 | ); 39 | } 40 | } -------------------------------------------------------------------------------- /Adapter/Bernard/ConsumerAdapter.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Adapter\Bernard; 12 | 13 | use Abc\Bundle\JobBundle\Job\Queue\ConsumerInterface; 14 | use Bernard\Consumer; 15 | use Bernard\QueueFactory; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class ConsumerAdapter implements ConsumerInterface 21 | { 22 | /** 23 | * @var Consumer 24 | */ 25 | private $consumer; 26 | 27 | /** 28 | * @var QueueFactory; 29 | */ 30 | private $queueFactory; 31 | 32 | /** 33 | * @param Consumer $consumer 34 | * @param QueueFactory $queueFactory 35 | */ 36 | public function __construct(Consumer $consumer, QueueFactory $queueFactory) 37 | { 38 | $this->consumer = $consumer; 39 | $this->queueFactory = $queueFactory; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function consume($queue, array $options = []) 46 | { 47 | $queue = $this->queueFactory->create($queue); 48 | 49 | $this->consumer->consume($queue, $options); 50 | } 51 | } -------------------------------------------------------------------------------- /Resources/docs/process-control.md: -------------------------------------------------------------------------------- 1 | Process Control 2 | =============== 3 | 4 | The AbcJobBundle integrates the [process control](https://github.com/aboutcoders/process-control) library and thereby makes it possible to inform jobs about external events such as process termination signals or [manual cancellation](./cancel-jobs.md) of the job. 5 | 6 | By default jobs are only informed if they have been [cancelled manually](./cancel-jobs.md) by the user. In order to also inform jobs about process signals sent to the long running [consumer commands](./message-consuming.md) you have to install the [AbcProcessControlBundle](https://github.com/aboutcoders/process-control-bundle). 7 | 8 | ## Process Control and SonataNotificationBundle 9 | 10 | If you are using the AbcJobBundle together with the [SonataNotificationBundle](https://github.com/sonata-project/SonataNotificationBundle) as message queue backend we recommend to also install the [AbcNotificationBundle](https://github.com/aboutcoders/notification-bundle). This bundle inherits from the [SonataNotificationBundle](https://github.com/sonata-project/SonataNotificationBundle) and integrates process control more deeply into [SonataNotificationBundle](https://github.com/sonata-project/SonataNotificationBundle) and thereby allows an even better control of [message consuming](./message-consuming.md). 11 | 12 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/Log.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/Fixtures/App/Bundle/TestBundle/Entity/Entity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Fixtures\App\Bundle\TestBundle\Entity; 12 | 13 | use Doctrine\ORM\Mapping as ORM; 14 | 15 | /** 16 | * @ORM\Table(name="abc_test_entity") 17 | * @ORM\Entity 18 | * @author Hannes Schulz 19 | */ 20 | class Entity 21 | { 22 | /** 23 | * @ORM\Id 24 | * @ORM\Column(type="integer") 25 | * @ORM\GeneratedValue(strategy="AUTO") 26 | * @var integer 27 | */ 28 | private $id; 29 | 30 | /** 31 | * @ORM\Column(name="name", type="string", unique=true) 32 | * @var string 33 | */ 34 | protected $name; 35 | 36 | /** 37 | * Get id 38 | * 39 | * @return integer 40 | */ 41 | public function getId() 42 | { 43 | return $this->id; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getName() 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * @param string $name 56 | */ 57 | public function setName($name) 58 | { 59 | $this->name = $name; 60 | } 61 | } -------------------------------------------------------------------------------- /Validator/Constraints/JobTypeValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobTypeRegistry; 14 | use Symfony\Component\Validator\Constraint; 15 | use Symfony\Component\Validator\ConstraintValidator; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class JobTypeValidator extends ConstraintValidator 21 | { 22 | /** 23 | * @var JobTypeRegistry 24 | */ 25 | private $registry; 26 | 27 | /** 28 | * @param JobTypeRegistry $registry 29 | */ 30 | public function __construct(JobTypeRegistry $registry) 31 | { 32 | $this->registry = $registry; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function validate($value, Constraint $constraint) 39 | { 40 | if (null === $value) { 41 | return; 42 | } 43 | 44 | 45 | if (!$this->registry->has($value)) { 46 | $this->context->buildViolation($constraint->message) 47 | ->setParameter('{{string}}', $value) 48 | ->addViolation(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Resources/config/doctrine/Schedule.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Entity/Job.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Doctrine\Job as BaseJob; 14 | use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class Job extends BaseJob 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function createSchedule($type, $expression) 25 | { 26 | return new Schedule($type, $expression); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function addSchedule(ScheduleInterface $schedule) 33 | { 34 | if(!$schedule instanceof Schedule) 35 | { 36 | $schedule = new Schedule($schedule->getType(), $schedule->getExpression()); 37 | } 38 | 39 | $schedule->setJob($this); 40 | 41 | parent::addSchedule($schedule); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function removeSchedule(ScheduleInterface $schedule) 48 | { 49 | if($schedule instanceof Schedule) 50 | { 51 | $schedule->setJob(null); 52 | } 53 | 54 | parent::removeSchedule($schedule); 55 | } 56 | } -------------------------------------------------------------------------------- /Job/Parameter/DefaultJobsConstraintProvider.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Parameter; 12 | 13 | use Abc\Bundle\JobBundle\Validator\Job\AbstractConstraintProvider; 14 | use Symfony\Component\Validator\Constraints as Assert; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class DefaultJobsConstraintProvider extends AbstractConstraintProvider 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function getConstraints($type) 25 | { 26 | switch ($type) { 27 | case 'abc.mailer': 28 | return $this->provideMailerConstraints(); 29 | break; 30 | case 'abc.sleeper': 31 | return $this->provideSleeperConstraints(); 32 | break; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | protected function provideMailerConstraints() 42 | { 43 | return [new Assert\NotBlank()]; 44 | } 45 | 46 | /** 47 | * @return array 48 | */ 49 | protected function provideSleeperConstraints() 50 | { 51 | return [new Assert\Range(['min' => 1])]; 52 | } 53 | } -------------------------------------------------------------------------------- /Resources/config/services/serializer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Validator/Constraints/JobValidator.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Constraints; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Symfony\Component\Validator\Constraint; 15 | use Symfony\Component\Validator\ConstraintValidator; 16 | use Abc\Bundle\JobBundle\Validator\Constraints as AssertJob; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class JobValidator extends ConstraintValidator 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function validate($value, Constraint $constraint) 27 | { 28 | if (null === $value) { 29 | return; 30 | } 31 | 32 | if(!$value instanceof JobInterface) { 33 | throw new \InvalidArgumentException('The value must be an instance of '.JobInterface::class); 34 | } 35 | 36 | if(null == $value->getType()) { 37 | return; 38 | } 39 | 40 | $this->context->getValidator() 41 | ->inContext($this->context) 42 | ->atPath('parameters') 43 | ->validate($value->getParameters(), new AssertJob\Parameters(['type' => $value->getType()])); 44 | 45 | return; 46 | } 47 | } -------------------------------------------------------------------------------- /Resources/docs/message-consuming.md: -------------------------------------------------------------------------------- 1 | Message Consuming 2 | ================= 3 | 4 | There are different ways to consume messages from the queues and process the jobs. 5 | 6 | ## Command Line 7 | 8 | The AbcJobBundle provides the symfony command `abc:job:consumer`. The command requires the name of the queue that messages will be consumed from as argument. The following command will consume messages from the `default` queue. 9 | 10 | ```bash 11 | php bin/console abc:job:consume default 12 | ``` 13 | 14 | In order to prevent out of memory errors the command should always be invoked with the option `max-iterations`. 15 | 16 | ```bash 17 | php bin/console abc:job:consume default --max-iterations=250 18 | ``` 19 | 20 | ## PHP 21 | 22 | The consumer command uses the underlying service `abc.job.consumer` to do its work. You can also use this service to consume and process jobs from the queue. 23 | 24 | ```php 25 | // retrieve job manager from the container 26 | $consumer = $container->get('abc.job.consumer'); 27 | 28 | $consumer->consume('default', [ 29 | 'max-iterations' => 250 30 | ]) 31 | ``` 32 | 33 | ## Supervisor 34 | 35 | In a production environment it is recommended to use a process control system like [supervisor](http://supervisord.org/) to monitor consumers and restart a process as soon as it stopped. 36 | 37 | In case you decided to use [supervisor](http://supervisord.org/) you might consider using the [AbcSupervisorBundle](https://github.com/aboutcoders/supervisor-bundle): 38 | 39 | Next Step: [Job Management](./job-management.md) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | 7 | matrix: 8 | fast_finish: true 9 | include: 10 | - php: 7.1 11 | env: SYMFONY_VERSION=3.3.* 12 | - php: 7.1 13 | env: SYMFONY_VERSION=3.4.* 14 | allow_failures: 15 | - php: 5.6 16 | 17 | sudo: false 18 | 19 | cache: 20 | directories: 21 | - $HOME/.composer/cache 22 | 23 | before_install: 24 | - composer self-update 25 | - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/symfony:"$SYMFONY_VERSION"; fi 26 | - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "memory_limit=3G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; 27 | 28 | install: 29 | - composer update $COMPOSER_FLAGS 30 | 31 | script: 32 | - vendor/bin/phpunit --coverage-clover clover 33 | 34 | notifications: 35 | hipchat: 36 | rooms: 37 | secure: uypSLOcHE2VlVWRlZFR0lwlj+1+I/oG10qAa3QA9LnJT6bIDGO9FBrKDvyhOhd/HmufQD35ytvrn2O3JUh9S25arjqoFEg4Or1MDymeUuSH6X1gV+pftDj/EEPTBZVNzpWOw+fCqASCmUsSFt6B9jd0QfnFMDbYKVaCS3Il+53+rRljvlCP55biFXOkHc4lsQ5FDJlal+5nmm8/SxGAN/IVnIfkHdHB+ljVKeU62F4OBLe0IiYvhmrgWt81D64zp96I5XvltvXT960QndIzVdp4rhB0L7GYxFiamiS9AhMcUVS4avlUwLbDtKQDe8VeKPxx2wsGwHD/zwjcs6QLxoyZfyofSRylhZYpuYLy938Wgl95euoA6gqfV4EtaTOkaVB/s1z6NuoAGPUEfFm8OvKJBxEX0/3hOsMdEsk5272WlTNyNRwNf0g2R2wS1y24OUZjSjroxKMZ3D77vX0xnmjzPecLkxpp5vGmNtBtP/eQ6PKwRPMPLBTUPsAlQzgU/BMQ/1WQKG9lBFHP62DXUlORxyZ6FuLat6gDTLayuPXNHoNFgTgnuCCx34n36MBSja1tQxY5ERglmzlk4tRARCCdww2TF0lFmgB00qesIzF0v3pXw/v84lj2r2XJ/cQFYDhZi3YF4VPLVVqxylOeREClWZ/xjYnDK/fm3JJpAf9M= -------------------------------------------------------------------------------- /Logger/Handler/HandlerFactoryRegistry.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Monolog\Handler\HandlerInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class HandlerFactoryRegistry 20 | { 21 | /** 22 | * @var array|BaseHandlerFactory[] 23 | */ 24 | private $factories = []; 25 | 26 | /** 27 | * @param HandlerFactoryInterface $factory 28 | */ 29 | public function register(HandlerFactoryInterface $factory) 30 | { 31 | $this->factories[] = $factory; 32 | } 33 | 34 | /** 35 | * Returns the handlers created by all registered factories. 36 | * 37 | * @param JobInterface $job 38 | * @param int $level The minimum logging level at which this handler will be triggered 39 | * @param boolean $bubble 40 | * @return array|HandlerInterface[] The created handlers 41 | */ 42 | public function createHandlers(JobInterface $job, $level, $bubble) 43 | { 44 | $handlers = []; 45 | foreach ($this->factories as $factory) { 46 | $handlers[] = $factory->createHandler($job, $level, $bubble); 47 | } 48 | 49 | return $handlers; 50 | } 51 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | ./Tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ./ 28 | 29 | ./build 30 | ./Resources 31 | ./Tests 32 | ./vendor 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Job/Queue/QueueConfig.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Queue; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | class QueueConfig implements QueueConfigInterface 17 | { 18 | /** 19 | * @var array 20 | */ 21 | private $map; 22 | 23 | /** 24 | * @var string 25 | */ 26 | private $defaultQueue; 27 | 28 | /** 29 | * @param array $queueMapping 30 | * @param string $defaultQueue 31 | */ 32 | public function __construct(array $queueMapping = [], $defaultQueue = 'default') 33 | { 34 | $this->defaultQueue = $defaultQueue; 35 | foreach ($queueMapping as $queueName => $jobTypes) { 36 | foreach ($jobTypes as $jobType) { 37 | if(!isset($this->map[$jobType])) { 38 | $this->map[$jobType] = $queueName; 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getDefaultQueue() 48 | { 49 | return $this->defaultQueue; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getQueue($type) 56 | { 57 | return isset($this->map[$type]) ? $this->map[$type] : $this->defaultQueue; 58 | } 59 | } -------------------------------------------------------------------------------- /Tests/Job/StatusTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\Status; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class StatusTest extends TestCase 20 | { 21 | public function testGetTerminatedStatus() 22 | { 23 | $values = [Status::CANCELLED, Status::PROCESSED, Status::ERROR]; 24 | foreach (Status::getTerminatedStatus() as $status) { 25 | /** 26 | * @var Status $status 27 | */ 28 | $this->assertContains($status->getValue(), $values); 29 | } 30 | } 31 | 32 | public function testGetUnterminatedStatus() 33 | { 34 | $values = [Status::REQUESTED, Status::PROCESSING, Status::CANCELLING, Status::SLEEPING]; 35 | foreach (Status::getUnterminatedStatus() as $status) { 36 | /** 37 | * @var Status $status 38 | */ 39 | $this->assertContains($status->getValue(), $values); 40 | } 41 | } 42 | 43 | public function testEquals() 44 | { 45 | $this->assertTrue(Status::PROCESSED()->equals(Status::PROCESSED())); 46 | $this->assertFalse(Status::PROCESSED()->equals(Status::CANCELLED())); 47 | } 48 | } -------------------------------------------------------------------------------- /Tests/Functional/Job/Mailer/MailerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Functional\Job\Mailer; 12 | 13 | use Abc\Bundle\JobBundle\Job\Mailer\Message; 14 | use Abc\Bundle\JobBundle\Test\JobTestCase; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class MailerTest extends JobTestCase 20 | { 21 | public function testRegistration() 22 | { 23 | $this->assertJobIsRegistered('abc.mailer', 'abc.job.mailer', 'send'); 24 | } 25 | 26 | public function testInvocation() 27 | { 28 | $message = new Message('mail@domain.tld', 'to@domain.td', 'Subject', 'MessageBody'); 29 | 30 | $this->assertInvokesJob('abc.mailer', [$message]); 31 | } 32 | 33 | public function testValidationSucceeds() 34 | { 35 | $message = new Message('mail@domain.tld', 'to@domain.td', 'Subject', 'MessageBody'); 36 | 37 | $this->assertTrue($this->assertValid('abc.mailer', [$message])); 38 | } 39 | 40 | public function testValidationFails() 41 | { 42 | $this->markTestSkipped('Skipped, because validation is not performed (need to investigate)'); 43 | 44 | $message = new Message(null, 'to@domain.td', 'Subject', 'MessageBody'); 45 | 46 | $this->assertTrue($this->assertNotValid('abc.mailer', [$message])); 47 | } 48 | } -------------------------------------------------------------------------------- /Logger/Handler/StreamHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Monolog\Handler\StreamHandler; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class StreamHandlerFactory extends BaseHandlerFactory 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $path; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $extension; 30 | 31 | /** 32 | * @param string $path 33 | * @param string $extension 34 | */ 35 | public function __construct($path, $extension = 'log') 36 | { 37 | $this->path = $path; 38 | $this->extension = $extension; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function createHandler(JobInterface $job, $level, $bubble) 45 | { 46 | $handler = new StreamHandler($this->buildPath($job->getTicket()), $level, $bubble); 47 | 48 | return $this->initHandler($handler); 49 | } 50 | 51 | /** 52 | * @param string $filename 53 | * @return string The path of the logfile 54 | */ 55 | private function buildPath($filename) 56 | { 57 | return $this->path . DIRECTORY_SEPARATOR . $filename . '.' . $this->extension; 58 | } 59 | } -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterDoctrineListenerPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use Gedmo\Timestampable\TimestampableListener; 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\Reference; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class RegisterDoctrineListenerPass implements CompilerPassInterface 22 | { 23 | public function process(ContainerBuilder $container) 24 | { 25 | if ('orm' == $container->getParameter('abc.job.db_driver')) { 26 | 27 | $connection = $container->getParameter('abc.job.connection'); 28 | 29 | $listener = $container->register('gedmo.listener.timestampable', TimestampableListener::class); 30 | $listener->addMethodCall('setAnnotationReader', [new Reference('annotation_reader')]); 31 | $listener->addTag('doctrine.event_subscriber', ['connection' => $connection]); 32 | 33 | $em = $container->getDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $container->getParameter('abc.job.connection'))); 34 | $em->addMethodCall('addEventSubscriber', array($listener)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Model/JobManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface as BaseJobInterface; 14 | use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface as BaseScheduleInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | abstract class JobManager implements JobManagerInterface 20 | { 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function create($type = null, $parameters = null, BaseScheduleInterface $schedule = null) 25 | { 26 | $class = $this->getClass(); 27 | 28 | /** @var JobInterface $job */ 29 | $job = new $class; 30 | 31 | $job->setType($type); 32 | $job->setParameters($parameters); 33 | 34 | if(!is_null($schedule)) 35 | { 36 | $job->addSchedule($schedule); 37 | } 38 | 39 | return $job; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public function findByTicket($ticket) 46 | { 47 | $jobs = $this->findBy(array('ticket' => $ticket)); 48 | 49 | return count($jobs) > 0 ? $jobs[0] : null; 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | public function isManagerOf(BaseJobInterface $job) 56 | { 57 | $class = $this->getClass(); 58 | 59 | return ($job instanceof $class); 60 | } 61 | } -------------------------------------------------------------------------------- /Tests/Model/JobTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Model; 12 | 13 | use Abc\Bundle\JobBundle\Model\Job; 14 | use Abc\Bundle\JobBundle\Model\Schedule; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class JobTest extends TestCase 18 | { 19 | public function testHasSchedule() 20 | { 21 | $job = new Job(); 22 | 23 | $this->assertFalse($job->hasSchedules()); 24 | 25 | $job->addSchedule(new Schedule()); 26 | 27 | $this->assertTrue($job->hasSchedules()); 28 | } 29 | 30 | public function testGetExecutionTime() 31 | { 32 | $subject = new Job(); 33 | $createdAt = new \DateTime('2010-01-01 00:00:00'); 34 | $terminatedAt = new \DateTime('2010-01-01 00:00:01'); 35 | 36 | $subject->setCreatedAt($createdAt); 37 | $subject->setTerminatedAt($terminatedAt); 38 | 39 | $expectedProcessingTime = $subject->getTerminatedAt()->format('U') - $subject->getCreatedAt()->format('U'); 40 | 41 | $this->assertEquals($expectedProcessingTime, $subject->getExecutionTime()); 42 | } 43 | 44 | public function testClone() 45 | { 46 | $job = new Job; 47 | $job->setTicket('ticket'); 48 | 49 | $clone = clone $job; 50 | 51 | $this->assertTrue($job !== $clone); 52 | $this->assertNotEquals($job->getTicket(), $clone->getTicket()); 53 | } 54 | } -------------------------------------------------------------------------------- /Job/Sleeper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | use Abc\Bundle\JobBundle\Annotation\ParamType; 14 | use Abc\ProcessControl\ControllerAwareInterface; 15 | use Abc\ProcessControl\ControllerInterface; 16 | use Psr\Log\LoggerInterface; 17 | 18 | /** 19 | * A test job to check if jobs can be cancelled at runtime 20 | * 21 | * @author Hannes Schulz 22 | */ 23 | class Sleeper implements ControllerAwareInterface 24 | { 25 | /** 26 | * @var ControllerInterface 27 | */ 28 | private $controller; 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function setController(ControllerInterface $controller) 34 | { 35 | $this->controller = $controller; 36 | } 37 | 38 | /** 39 | * @ParamType("seconds", type="integer") 40 | * @ParamType("logger", type="@abc.logger") 41 | * @param $seconds 42 | * @param LoggerInterface $logger 43 | */ 44 | public function sleep($seconds, LoggerInterface $logger) 45 | { 46 | $logger->info('start sleeping for {seconds}', ['seconds' => $seconds]); 47 | 48 | $start = time(); 49 | do { 50 | 51 | sleep(1); 52 | $seconds--; 53 | 54 | } while ($seconds > 0 && !$this->controller->doStop()); 55 | 56 | $logger->info('stopped sleeping after {seconds}', ['seconds' => time() - $start]); 57 | } 58 | } -------------------------------------------------------------------------------- /Resources/config/services/adapter_bernard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Job.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Event/JobEvents.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Event; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | final class JobEvents 17 | { 18 | 19 | /** 20 | * The abc.job.message_consume event is triggered each time a message is consumed from the queue 21 | * 22 | * The event listener receives an event of type Symfony\Component\EventDispatcher\Event 23 | * 24 | * @var string 25 | */ 26 | const JOB_MESSAGE_CONSUME = 'abc.job.message_consume'; 27 | 28 | /** 29 | * The abc.job.pre_execute event is triggered each time before a job is executed 30 | * 31 | * The event listener receives an event of type Abc\Bundle\JobBundle\Event\ExecutionEvent 32 | * 33 | * @var string 34 | */ 35 | const JOB_PRE_EXECUTE = 'abc.job.pre_execute'; 36 | 37 | /** 38 | * The abc.job.post_execute event is triggered each time before a job is executed 39 | * 40 | * The event listener receives an event of type Abc\Bundle\JobBundle\Event\ExecutionEvent 41 | * 42 | * @var string 43 | */ 44 | const JOB_POST_EXECUTE = 'abc.job.post_execute'; 45 | 46 | /** 47 | * The abc.job.terminated event is triggered whenever a root job terminates 48 | * 49 | * The event listener receives an event of type Abc\Bundle\JobBundle\Event\TerminationEvent 50 | * 51 | * @var string 52 | */ 53 | const JOB_TERMINATED = 'abc.job.terminated'; 54 | } -------------------------------------------------------------------------------- /Model/JobInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\JobBundle\Job\Status; 14 | use Abc\Bundle\JobBundle\Job\JobInterface as BaseJobInterface; 15 | 16 | /** 17 | * Defines API of a (persistable) job entity. 18 | * 19 | * @author Hannes Schulz 20 | */ 21 | interface JobInterface extends BaseJobInterface 22 | { 23 | /** 24 | * @param string $ticket 25 | * @return void 26 | */ 27 | public function setTicket($ticket); 28 | 29 | /** 30 | * @param string $type 31 | * @return void 32 | */ 33 | public function setType($type); 34 | 35 | /** 36 | * @param Status $status 37 | * @return void 38 | */ 39 | public function setStatus(Status $status); 40 | 41 | /** 42 | * Sets the response of the root job 43 | * 44 | * @param mixed|null $response The serialized response 45 | */ 46 | public function setResponse($response = null); 47 | 48 | /** 49 | * @param integer $milliseconds The processing time in milliseconds 50 | * @return void 51 | */ 52 | public function setProcessingTime($milliseconds); 53 | 54 | /** 55 | * @param \DateTime $createdAt 56 | * @return void 57 | */ 58 | public function setCreatedAt(\DateTime $createdAt); 59 | 60 | /** 61 | * @param \DateTime $terminatedAt 62 | * @return void 63 | */ 64 | public function setTerminatedAt(\DateTime $terminatedAt); 65 | } -------------------------------------------------------------------------------- /Model/LogManagerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\JobBundle\Job\LogManagerInterface as BaseLogManagerInterface; 14 | use Monolog\Formatter\FormatterInterface; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | interface LogManagerInterface extends BaseLogManagerInterface 20 | { 21 | /** 22 | * @return string The fully qualified class name of the entity class. 23 | */ 24 | public function getClass(); 25 | 26 | /** 27 | * @return LogInterface 28 | */ 29 | public function create(); 30 | 31 | /** 32 | * @param LogInterface $log 33 | * @param bool $andFlush Whether to flush the changes (default true) 34 | * @return void 35 | */ 36 | public function save(LogInterface $log, $andFlush = true); 37 | 38 | /** 39 | * @return LogInterface[] 40 | */ 41 | public function findAll(); 42 | 43 | /** 44 | * 45 | * @param array $criteria 46 | * @param array|null $orderBy 47 | * @param int|null $limit 48 | * @param int|null $offset 49 | * @throws \UnexpectedValueException 50 | * @return LogInterface[] 51 | */ 52 | public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null); 53 | 54 | /** 55 | * @param LogInterface $log 56 | * @return int The number of deleted entities 57 | */ 58 | public function delete(LogInterface $log); 59 | } -------------------------------------------------------------------------------- /Api/BadRequestResponse.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Api; 12 | 13 | use JMS\Serializer\Annotation\Type; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class BadRequestResponse 19 | { 20 | /** 21 | * @Type("string") 22 | * @var string 23 | */ 24 | protected $message; 25 | 26 | /** 27 | * @Type("string") 28 | * @var string 29 | */ 30 | protected $description; 31 | 32 | /** 33 | * ype("array") 34 | * @var array 35 | */ 36 | protected $errors; 37 | 38 | /** 39 | * @param string $message 40 | * @param string $description 41 | */ 42 | public function __construct($message, $description) 43 | { 44 | $this->message = $message; 45 | $this->description = $description; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getMessage() 52 | { 53 | return $this->message; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getDescription() 60 | { 61 | return $this->description; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getErrors() 68 | { 69 | return $this->errors; 70 | } 71 | 72 | /** 73 | * @param array $errors 74 | * @return void 75 | */ 76 | public function setErrors($errors) 77 | { 78 | $this->errors = $errors; 79 | } 80 | } -------------------------------------------------------------------------------- /Resources/config/services/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Abc\Bundle\JobBundle\Entity\Job 9 | Abc\Bundle\JobBundle\Entity\Schedule 10 | default 11 | 12 | 13 | 14 | 15 | 16 | 17 | %abc.job.model.job.class% 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | %abc.job.model.schedule.class% 26 | 27 | 28 | 29 | 30 | %abc.job.model_manager_name% 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Serializer/EventDispatcher/JobDeserializationSubscriber.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Serializer\EventDispatcher; 12 | 13 | use Abc\Bundle\JobBundle\Model\Job; 14 | use JMS\Serializer\EventDispatcher\EventSubscriberInterface; 15 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class JobDeserializationSubscriber implements EventSubscriberInterface 21 | { 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public static function getSubscribedEvents() 26 | { 27 | return [ 28 | ['event' => 'serializer.pre_deserialize', 'method' => 'onPreDeserialize'] 29 | ]; 30 | } 31 | 32 | /** 33 | * Appends an array element to the parameters of a job that provides information about the job type. 34 | * 35 | * @param PreDeserializeEvent $event 36 | */ 37 | public function onPreDeserialize(PreDeserializeEvent $event) 38 | { 39 | $type = $event->getType(); 40 | if (isset($type['name']) && ($type['name'] == Job::class || is_subclass_of($type['name'], Job::class))) { 41 | $data = $event->getData(); 42 | if (isset($data['type']) && isset($data['parameters']) && is_array($data['parameters']) && count($data['parameters']) > 0) { 43 | array_push($data['parameters'], ['abc.job.type' => $data['type']]); 44 | $event->setData($data); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Entity/LogManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Doctrine\LogManager as BaseLogManager; 14 | use Abc\Bundle\JobBundle\Model\LogInterface; 15 | use Doctrine\Common\Proxy\Exception\InvalidArgumentException; 16 | use Doctrine\ORM\EntityManager; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class LogManager extends BaseLogManager 22 | { 23 | /** @var EntityManager */ 24 | protected $em; 25 | 26 | /** 27 | * @param EntityManager $em 28 | * @param string $class 29 | */ 30 | public function __construct(EntityManager $em, $class) 31 | { 32 | parent::__construct($em, $class); 33 | 34 | $this->em = $em; 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function save(LogInterface $log, $andFlush = true) 41 | { 42 | if(!$log instanceof $this->class) 43 | { 44 | throw new InvalidArgumentException('1st argument must be an instanceof '.$this->getClass()); 45 | } 46 | 47 | $extra = $log->getExtra(); 48 | if(is_array($extra) && isset($extra['job_ticket'])) 49 | { 50 | /** @var \Abc\Bundle\JobBundle\Entity\Log $log */ 51 | $log->setJobTicket($extra['job_ticket']); 52 | 53 | unset($extra['job_ticket']); 54 | 55 | $log->setExtra($extra); 56 | } 57 | 58 | parent::save($log, $andFlush); 59 | } 60 | } -------------------------------------------------------------------------------- /Doctrine/ScheduleManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Doctrine; 12 | 13 | use Abc\Bundle\JobBundle\Model\ScheduleInterface; 14 | use Abc\Bundle\JobBundle\Model\ScheduleManagerInterface; 15 | use Abc\Bundle\SchedulerBundle\Doctrine\ScheduleManager as BaseScheduleManager; 16 | 17 | /** 18 | * Doctrine EntityManager for entities of type Abc\Bundle\JobBundle\Model\ScheduleInterface 19 | * 20 | * @author Hannes Schulz 21 | */ 22 | class ScheduleManager extends BaseScheduleManager implements ScheduleManagerInterface 23 | { 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function create($type = null, $expression = null, $active = true) 28 | { 29 | $class = $this->getClass(); 30 | 31 | /** @var ScheduleInterface $schedule */ 32 | $schedule = new $class; 33 | $schedule->setType($type); 34 | $schedule->setExpression($expression); 35 | $schedule->setIsActive($active == null ? true : $active); 36 | 37 | return $schedule; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function findSchedules($limit = null, $offset = null) 44 | { 45 | return $this->repository->findBy(array('isActive' => true), array(), $limit, $offset); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function delete(ScheduleInterface $schedule) 52 | { 53 | $this->objectManager->remove($schedule); 54 | $this->objectManager->flush(); 55 | } 56 | } -------------------------------------------------------------------------------- /Job/Context/ContextInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Context; 12 | 13 | use Abc\Bundle\JobBundle\Job\Context\Exception\ParameterNotFoundException; 14 | 15 | /** 16 | * ContextInterface 17 | * 18 | * @author Hannes Schulz 19 | */ 20 | interface ContextInterface 21 | { 22 | 23 | /** 24 | * Gets the context parameters. 25 | * 26 | * @return array An array of parameters 27 | */ 28 | public function all(); 29 | 30 | /** 31 | * Clears all parameters. 32 | * 33 | * @return void 34 | */ 35 | public function clear(); 36 | 37 | /** 38 | * Gets a context parameter. 39 | * 40 | * @param string $name The parameter name 41 | * @return mixed The parameter value 42 | * @throws ParameterNotFoundException if the parameter is not defined 43 | */ 44 | public function get($name); 45 | 46 | /** 47 | * Sets a context parameter. 48 | * 49 | * @param string $name The parameter name 50 | * @param mixed $value The parameter value 51 | * @return void 52 | */ 53 | public function set($name, $value); 54 | 55 | /** 56 | * Returns true if a parameter name is defined. 57 | * 58 | * @param string $name The parameter name 59 | * @return bool Returns true if the parameter name is defined otherwise false 60 | */ 61 | public function has($name); 62 | 63 | /** 64 | * @param string $name The parameter name 65 | * @return void 66 | */ 67 | public function remove($name); 68 | } -------------------------------------------------------------------------------- /Model/LogManager.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface as BaseJobInterface; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | abstract class LogManager implements LogManagerInterface 19 | { 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | public function create() 24 | { 25 | $class = $this->getClass(); 26 | 27 | /** 28 | * @var LogInterface $log 29 | */ 30 | return new $class; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function findByJob(BaseJobInterface $job) 37 | { 38 | $records = array(); 39 | foreach ($this->findBy(['jobTicket' => $job->getTicket()], ['datetime' => 'ASC']) as $log) { 40 | /** 41 | * @var LogInterface $log 42 | */ 43 | $records[] = $log->toRecord(); 44 | } 45 | 46 | return $records; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function deleteByJob(BaseJobInterface $job) 53 | { 54 | return $this->deleteLogs($this->findBy(['jobTicket' => $job->getTicket()])); 55 | } 56 | 57 | /** 58 | * @param LogInterface[] $logs 59 | * @return int the number of delete entries 60 | */ 61 | protected function deleteLogs($logs) 62 | { 63 | $i = 0; 64 | foreach ($logs as $log) { 65 | $this->delete($log); 66 | $i++; 67 | } 68 | 69 | return $i; 70 | } 71 | } -------------------------------------------------------------------------------- /DependencyInjection/Compiler/LockerConfigurationPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use Abc\Bundle\JobBundle\Locker\NullLocker; 14 | use Abc\Bundle\ResourceLockBundle\Model\LockInterface; 15 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Definition; 18 | 19 | /** 20 | * @author Hannes Schulz 21 | */ 22 | class LockerConfigurationPass implements CompilerPassInterface 23 | { 24 | public function process(ContainerBuilder $container) 25 | { 26 | if($container->getParameter('abc.job.locker_service') == 'abc.job.locker.default') 27 | { 28 | if(!$container->hasParameter('abc.resource_lock.lock_manager.class')) 29 | { 30 | $container->removeDefinition('abc.job.locker.default'); 31 | $container->setDefinition('abc.job.locker', new Definition(NullLocker::class)); 32 | } 33 | } 34 | else { 35 | $class = $container->getParameterBag()->resolveValue( 36 | $container->findDefinition($container->getParameter('abc.job.locker_service'))->getClass() 37 | ); 38 | 39 | if (!in_array(LockInterface::class, class_implements($class))) { 40 | throw new \InvalidArgumentException(sprintf('"abc.job.locker" must implement %s (instance of %s given).', LockInterface::class, $class)); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Tests/Job/ExceptionResponseTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Job\Response; 12 | 13 | use Abc\Bundle\JobBundle\Job\ExceptionResponse; 14 | use JMS\Serializer\SerializerBuilder; 15 | use JMS\Serializer\SerializerInterface; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class ExceptionResponseTest extends TestCase 22 | { 23 | /** 24 | * @var SerializerInterface 25 | */ 26 | private $serializer; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function setUp() 32 | { 33 | $this->serializer = SerializerBuilder::create()->build(); 34 | } 35 | 36 | public function testGetCode() 37 | { 38 | $exception = new \Exception('foobar', 100); 39 | $subject = new ExceptionResponse($exception); 40 | 41 | $this->assertSame(100, $subject->getCode()); 42 | } 43 | 44 | public function testGetMessage() 45 | { 46 | $exception = new \Exception('foobar', 100); 47 | $subject = new ExceptionResponse($exception); 48 | 49 | $this->assertSame('foobar', $subject->getMessage()); 50 | } 51 | 52 | public function testSerializationToJson() 53 | { 54 | $exception = new \Exception('foobar', 100); 55 | $subject = new ExceptionResponse($exception); 56 | 57 | $data = $this->serializer->serialize($subject, 'json'); 58 | 59 | $object = $this->serializer->deserialize($data, ExceptionResponse::class, 'json'); 60 | 61 | $this->assertEquals($subject, $object); 62 | } 63 | } -------------------------------------------------------------------------------- /Test/AdapterTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Test; 12 | 13 | use Abc\Bundle\JobBundle\Job\ManagerInterface; 14 | use Abc\Bundle\JobBundle\Job\Queue\ConsumerInterface; 15 | use Abc\Bundle\JobBundle\Job\Status; 16 | use Abc\Bundle\JobBundle\Test\DatabaseKernelTestCase; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | abstract class AdapterTest extends DatabaseKernelTestCase 22 | { 23 | public function setUp() 24 | { 25 | $this->setKernelOptions(['environment' => $this->getEnvironment()]); 26 | parent::setUp(); 27 | } 28 | 29 | public function testProduceAndConsume() 30 | { 31 | $ticket = $this->getJobManager()->addJob('log', array('message')); 32 | 33 | $this->processJobs(); 34 | 35 | $this->assertEquals(Status::PROCESSED(), $this->getJobManager()->get($ticket)->getStatus()); 36 | } 37 | 38 | /** 39 | * @return string The name of the environment 40 | */ 41 | public abstract function getEnvironment(); 42 | 43 | /** 44 | * @return ManagerInterface 45 | */ 46 | protected function getJobManager() 47 | { 48 | return $this->getContainer()->get('abc.job.manager'); 49 | } 50 | 51 | /** 52 | * @return void 53 | */ 54 | protected function processJobs() 55 | { 56 | /** 57 | * @var ConsumerInterface $consumer 58 | */ 59 | $consumer = $this->getContainer()->get('abc.job.consumer'); 60 | $consumer->consume('default', [ 61 | 'stop-when-empty' => true 62 | ]); 63 | } 64 | } -------------------------------------------------------------------------------- /Tests/Fixtures/App/app/config/config_test.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: services.yml } 3 | # - { resource: parameters.yml } 4 | 5 | framework: 6 | secret: Hell yeah! 7 | router: { resource: "%kernel.root_dir%/config/routing.yml" } 8 | form: true 9 | csrf_protection: true 10 | templating: 11 | engines: ['twig'] 12 | test: ~ 13 | session: 14 | storage_id: session.storage.mock_file 15 | default_locale: en 16 | translator: { fallback: en } 17 | profiler: 18 | collect: false 19 | validation: { enable_annotations: true } 20 | 21 | monolog: 22 | handlers: 23 | main: 24 | type: stream 25 | path: "%kernel.logs_dir%/%kernel.environment%.log" 26 | level: error 27 | channels: ['!abc.job'] 28 | abc_job: 29 | type: stream 30 | path: "%kernel.logs_dir%/abc_job_%kernel.environment%.log" 31 | level: debug 32 | channels: ["abc.job"] 33 | 34 | # Twig Configuration 35 | twig: 36 | debug: "%kernel.debug%" 37 | strict_variables: "%kernel.debug%" 38 | 39 | doctrine: 40 | dbal: 41 | driver: "pdo_sqlite" 42 | path: "%kernel.cache_dir%/sqlite.db" 43 | types: 44 | json: Sonata\Doctrine\Types\JsonType 45 | 46 | orm: 47 | auto_generate_proxy_classes: true 48 | auto_mapping: true 49 | 50 | swiftmailer: 51 | disable_delivery: true 52 | 53 | bernard: 54 | driver: doctrine 55 | 56 | abc_resource_lock: 57 | db_driver: orm 58 | 59 | abc_scheduler: 60 | db_driver: orm 61 | 62 | abc_job: 63 | adapter: sonata 64 | register_default_jobs: true 65 | logging: 66 | storage_handler: 67 | type: orm # Choose "orm" if you want to store job logs in the database instead of files -------------------------------------------------------------------------------- /Resources/docs/cancel-jobs.md: -------------------------------------------------------------------------------- 1 | Cancel Jobs 2 | =========== 3 | 4 | In some cases it is necessary to cancel a job either manually or if for example a new version of the application is deployed. By default a job cannot be cancelled at runtime unless a process termination signal kills the underlying PHP process. 5 | 6 | To make job cancellable the job class must implement the interface ControllerAwareInterface: 7 | 8 | ```php 9 | namespace Abc\ProcessControl; 10 | 11 | use Abc\ProcessControl\ControllerInterface; 12 | 13 | interface ControllerAwareInterface 14 | { 15 | public function setController(ControllerInterface $controller); 16 | } 17 | ``` 18 | 19 | This `ControllerAwareInterface` defines the method `doExit()` which indicates whether the job should abort it's execution: 20 | 21 | ```php 22 | namespace Abc\ProcessControl; 23 | 24 | interface ControllerInterface 25 | { 26 | public function doExit(); 27 | } 28 | ``` 29 | 30 | __Note:__ It is recommended to implement this interface in every job that performs work for a longer period of time (> 1 second) in order to prevent uncontrolled termination of jobs and in order support manual cancellation of jobs. 31 | 32 | Below you see an example implementation how a job uses the controller: 33 | 34 | ```php 35 | use Abc\ProcessControl\ControllerAwareInterface; 36 | use Abc\ProcessControl\ControllerInterface; 37 | 38 | class Sleeper implements ControllerAwareInterface 39 | { 40 | private $controller; 41 | 42 | public function setController(ControllerInterface $controller) 43 | { 44 | $this->controller = $controller; 45 | } 46 | 47 | /** 48 | * @ParamType("seconds", type="integer") 49 | * @ParamType("logger", type="@abc.logger") 50 | */ 51 | public function sleep($seconds, LoggerInterface $logger) 52 | { 53 | // ... 54 | } 55 | } 56 | ``` 57 | 58 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Resources/config/services/registry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Metadata\MetadataFactory 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | %abc.job.queue_config% 31 | %abc.job.default_queue% 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Tests/Logger/Handler/StreamHandlerFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Logger\Handler\StreamHandlerFactory; 14 | use Abc\Bundle\JobBundle\Model\Job; 15 | use Monolog\Handler\StreamHandler; 16 | use Monolog\Logger; 17 | use org\bovigo\vfs\vfsStream; 18 | use org\bovigo\vfs\vfsStreamDirectory; 19 | use PHPUnit\Framework\TestCase; 20 | 21 | /** 22 | * @author Hannes Schulz 23 | */ 24 | class StreamHandlerFactoryTest extends TestCase 25 | { 26 | /** 27 | * @var vfsStreamDirectory 28 | */ 29 | private $root; 30 | 31 | /** 32 | * @var StreamHandlerFactory 33 | */ 34 | private $subject; 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function setUp() 40 | { 41 | $this->root = vfsStream::setup(); 42 | $this->subject = new StreamHandlerFactory($this->root->url()); 43 | } 44 | 45 | /** 46 | * @param int $level 47 | * @param boolean $bubble 48 | * @dataProvider provideLevels 49 | */ 50 | public function testCreateHandler($level, $bubble) 51 | { 52 | $job = new Job(); 53 | $job->setTicket('JobTicket'); 54 | 55 | $handler = $this->subject->createHandler($job, $level, $bubble); 56 | $this->assertInstanceOf(StreamHandler::class, $handler); 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public static function provideLevels() 63 | { 64 | return [ 65 | [Logger::CRITICAL, true], 66 | [Logger::CRITICAL, false] 67 | ]; 68 | } 69 | } -------------------------------------------------------------------------------- /Tests/Functional/Job/ConnectionTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Functional\Job; 12 | 13 | use Abc\Bundle\JobBundle\Job\ManagerInterface; 14 | use Abc\Bundle\JobBundle\Job\Queue\ConsumerInterface; 15 | use Abc\Bundle\JobBundle\Job\Status; 16 | use Abc\Bundle\JobBundle\Test\DatabaseKernelTestCase; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class ConnectionTest extends DatabaseKernelTestCase 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function setUp() 27 | { 28 | $this->setEntityManagerNames(['default', 'abc_job_processing']); 29 | $this->setKernelOptions(['environment' => 'dedicated_connection']); 30 | parent::setUp(); 31 | } 32 | 33 | public function testWithDedicatedConnection() 34 | { 35 | $job = $this->getJobManager()->addJob('throw_dbal_exception'); 36 | 37 | $this->processJobs(); 38 | 39 | $this->getEntityManager()->clear(); 40 | 41 | $this->assertEquals(Status::ERROR(), $job->getStatus()); 42 | } 43 | 44 | /** 45 | * @return ManagerInterface 46 | */ 47 | protected function getJobManager() 48 | { 49 | return $this->getContainer()->get('abc.job.manager'); 50 | } 51 | 52 | /** 53 | * @return void 54 | */ 55 | protected function processJobs() 56 | { 57 | /** 58 | * @var ConsumerInterface $consumer 59 | */ 60 | $consumer = $this->getContainer()->get('abc.job.consumer'); 61 | $consumer->consume('default', [ 62 | 'stop-when-empty' => true 63 | ]); 64 | } 65 | } -------------------------------------------------------------------------------- /Tests/Logger/Handler/OrmHandlerFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Logger\Handler\JobAwareOrmHandler; 14 | use Abc\Bundle\JobBundle\Logger\Handler\OrmHandlerFactory; 15 | use Abc\Bundle\JobBundle\Model\Job; 16 | use Abc\Bundle\JobBundle\Model\LogManagerInterface; 17 | use Monolog\Logger; 18 | use PHPUnit\Framework\TestCase; 19 | 20 | /** 21 | * @author Hannes Schulz 22 | */ 23 | class OrmHandlerFactoryTest extends TestCase 24 | { 25 | /** 26 | * @var LogManagerInterface|\PHPUnit_Framework_MockObject_MockObject 27 | */ 28 | private $manager; 29 | 30 | /** 31 | * @var OrmHandlerFactory 32 | */ 33 | private $subject; 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function setUp() 39 | { 40 | $this->manager = $this->createMock(LogManagerInterface::class); 41 | $this->subject = new OrmHandlerFactory($this->manager); 42 | } 43 | 44 | /** 45 | * @param int $level 46 | * @param boolean $bubble 47 | * @dataProvider provideLevels 48 | */ 49 | public function testCreateHandler($level, $bubble) 50 | { 51 | $job = new Job(); 52 | $handler = $this->subject->createHandler($job, $level, $bubble); 53 | 54 | $this->assertInstanceOf(JobAwareOrmHandler::class, $handler); 55 | } 56 | 57 | /** 58 | * @return array 59 | */ 60 | public static function provideLevels() 61 | { 62 | return [ 63 | [Logger::CRITICAL, true], 64 | [Logger::CRITICAL, false] 65 | ]; 66 | } 67 | } -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ControllerConfigurationPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use Abc\ProcessControl\ControllerInterface; 14 | use Abc\ProcessControl\NullController; 15 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 16 | use Symfony\Component\DependencyInjection\ContainerBuilder; 17 | use Symfony\Component\DependencyInjection\Definition; 18 | 19 | /** 20 | * @author Hannes Schulz 21 | */ 22 | class ControllerConfigurationPass implements CompilerPassInterface 23 | { 24 | public function process(ContainerBuilder $container) 25 | { 26 | $controllerService = $container->getParameter('abc.job.controller_service'); 27 | 28 | if ($controllerService != 'abc.process_control.controller') { 29 | 30 | $class = $container->getParameterBag()->resolveValue( 31 | $container->findDefinition($controllerService)->getClass() 32 | ); 33 | 34 | if (!in_array(ControllerInterface::class, class_implements($class))) { 35 | throw new \InvalidArgumentException(sprintf('"abc.job.controller" must implement %s (instance of %s given).', ControllerInterface::class, $class)); 36 | } 37 | } elseif ($container->hasDefinition('abc.process_control.controller') || $container->hasAlias('abc.process_control.controller')) { 38 | $container->setAlias('abc.job.controller', 'abc.process_control.controller'); 39 | } 40 | else { 41 | $container->setDefinition('abc.job.controller', new Definition(NullController::class)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterConstraintProvidersPass.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler; 12 | 13 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class RegisterConstraintProvidersPass implements CompilerPassInterface 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $validator; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $tag; 30 | 31 | /** 32 | * @param string $validator Service name of the definition registry in processed container 33 | * @param string $tag The tag name used for jobs 34 | */ 35 | public function __construct($validator = 'abc.job.validator.parameters', $tag = 'abc.job.constraint_provider') 36 | { 37 | $this->validator = $validator; 38 | $this->tag = $tag; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function process(ContainerBuilder $container) 45 | { 46 | if (!$container->hasDefinition($this->validator) && !$container->hasAlias($this->validator)) { 47 | return; 48 | } 49 | 50 | $validator = $container->findDefinition($this->validator); 51 | foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { 52 | $definition = $container->getDefinition($id); 53 | 54 | foreach ($tags as $tag) { 55 | $validator->addMethodCall('register', array($definition)); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Entity/Schedule.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Entity; 12 | 13 | use Abc\Bundle\JobBundle\Model\JobInterface; 14 | use Abc\Bundle\JobBundle\Model\Schedule as BaseSchedule; 15 | use JMS\Serializer\Annotation as JMS; 16 | 17 | /** 18 | * @JMS\ExclusionPolicy("all") 19 | * 20 | * @author Hannes Schulz 21 | */ 22 | class Schedule extends BaseSchedule 23 | { 24 | /** 25 | * @var integer 26 | */ 27 | protected $id; 28 | 29 | /** 30 | * @var string 31 | */ 32 | protected $jobTicket; 33 | 34 | /** 35 | * @var JobInterface 36 | */ 37 | protected $job; 38 | 39 | /** 40 | * @param int $id 41 | * @return void 42 | */ 43 | public function setId($id) 44 | { 45 | $this->id = $id; 46 | } 47 | 48 | /** 49 | * @return integer 50 | */ 51 | public function getId() 52 | { 53 | return $this->id; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getJobTicket() 60 | { 61 | return $this->jobTicket; 62 | } 63 | 64 | /** 65 | * @return JobInterface|null 66 | */ 67 | public function getJob() 68 | { 69 | return $this->job; 70 | } 71 | 72 | /** 73 | * @param JobInterface|null $job 74 | */ 75 | public function setJob(JobInterface $job = null) 76 | { 77 | $this->job = $job; 78 | } 79 | 80 | /** 81 | * Override clone in order to avoid duplicating entries in Doctrine 82 | */ 83 | public function __clone() 84 | { 85 | parent::__clone(); 86 | 87 | $this->id = null; 88 | } 89 | } -------------------------------------------------------------------------------- /Resources/docs/serialization.md: -------------------------------------------------------------------------------- 1 | Serialization 2 | ============= 3 | 4 | Serialization of parameters and return values of a job happens in two places. First place is when a job is persisted to the database. When this happens the parameters and return value of a job is serialized and persisted to the database together with the rest of the data of a job. The second place is the REST-Api. In this case the whole job instance including parameters and response are serialized. 5 | 6 | The AbcJobBundle uses the [JMS serializer](https://github.com/schmittjoh/serializer) by default for serialization. 7 | 8 | ## Serialization Options 9 | 10 | You can configure serialization options for the parameters and return value of a job. 11 | 12 | ```php 13 | namespace My\Bundle\ExampleBundle\Job\MyJob; 14 | 15 | use Abc\Bundle\JobBundle\Annotation\JobParameters; 16 | use My\Bundle\ExampleBundle\Entity\MyEntity; 17 | 18 | class MyJob 19 | { 20 | private $entityManager; 21 | 22 | /** 23 | * @ParamType("entity", type="My\Bundle\ExampleBundle\Entity\MyEntity", options={"groups"={"primarykey"}, "version"="1"}) 24 | * @ReturnType("My\Bundle\ExampleBundle\Model\SomeObject", options={"groups"={"mygroup"}, "version"="2") 25 | */ 26 | public function doSomething($entity) 27 | { 28 | if(!$this->entityManager->contains($entity) 29 | { 30 | $entity = $this->entityManager->findByPK($entity::class, $entity->getId()) 31 | } 32 | 33 | // ... 34 | 35 | return $someObject; 36 | } 37 | } 38 | ``` 39 | __Note:__ The serialization groups are only applied when a job is persisted and loaded from the database. The reason for this is that the [JMS serializer](https://github.com/schmittjoh/serializer) so far does not support the definition of serialization groups for specific properties of an entity but only globally for the whole serialization of an object. 40 | 41 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Logger/Handler/BaseHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobInterface; 14 | use Abc\Bundle\SchedulerBundle\Schedule\ProcessorInterface; 15 | use Monolog\Formatter\FormatterInterface; 16 | use Monolog\Handler\HandlerInterface; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | abstract class BaseHandlerFactory implements HandlerFactoryInterface 22 | { 23 | /** 24 | * @var FormatterInterface 25 | */ 26 | protected $formatter; 27 | 28 | /** 29 | * @var array|ProcessorInterface[] 30 | */ 31 | protected $processors = array(); 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public abstract function createHandler(JobInterface $job, $level, $bubble); 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function setFormatter(FormatterInterface $formatter = null) 42 | { 43 | $this->formatter = $formatter; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function setProcessors(array $processors) 50 | { 51 | $this->processors = $processors; 52 | } 53 | 54 | /** 55 | * Sets formatter and processors. 56 | * 57 | * @param HandlerInterface $handler 58 | * @return HandlerInterface 59 | */ 60 | protected function initHandler(HandlerInterface $handler) 61 | { 62 | if ($this->formatter != null) { 63 | $handler->setFormatter($this->formatter); 64 | } 65 | 66 | foreach ($this->processors as $processor) { 67 | $handler->pushProcessor($processor); 68 | } 69 | 70 | return $handler; 71 | } 72 | } -------------------------------------------------------------------------------- /Tests/Adapter/Bernard/ConsumerAdapterTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Adapter\Bernard; 12 | 13 | use Abc\Bundle\JobBundle\Adapter\Bernard\ConsumerAdapter; 14 | use Bernard\Consumer; 15 | use Bernard\Queue; 16 | use Bernard\QueueFactory; 17 | use PHPUnit\Framework\TestCase; 18 | 19 | /** 20 | * @author Hannes Schulz 21 | */ 22 | class ConsumerAdapterTest extends TestCase 23 | { 24 | /** 25 | * @var Consumer|\PHPUnit_Framework_MockObject_MockObject 26 | */ 27 | private $consumer; 28 | 29 | /** 30 | * @var QueueFactory|\PHPUnit_Framework_MockObject_MockObject 31 | */ 32 | private $queueFactory; 33 | 34 | /** 35 | * @var ConsumerAdapter 36 | */ 37 | private $subject; 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function setUp() 43 | { 44 | $this->consumer = $this->getMockBuilder(Consumer::class)->disableOriginalConstructor()->getMock(); 45 | $this->queueFactory = $this->getMockBuilder(QueueFactory::class)->disableOriginalConstructor()->getMock(); 46 | $this->subject = new ConsumerAdapter($this->consumer, $this->queueFactory); 47 | } 48 | 49 | public function testConsume() { 50 | 51 | $queue = $this->createMock(Queue::class); 52 | $options = array('foo' => 'bar'); 53 | 54 | $this->queueFactory->expects($this->once()) 55 | ->method('create') 56 | ->with('foobar') 57 | ->willReturn($queue); 58 | 59 | $this->consumer->expects($this->once()) 60 | ->method('consume') 61 | ->with($queue, $options); 62 | 63 | $this->subject->consume('foobar', $options); 64 | } 65 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aboutcoders/job-bundle", 3 | "type": "symfony-bundle", 4 | "description": "A symfony bundle for asynchronous job processing.", 5 | "keywords": ["job", "jobs", "schedule", "scheduler", "cron", "cronjob", "bundle", "queue", "task", "background", "rest", "rest-api" , "asynchronous"], 6 | "homepage": "http://aboutcoders.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Hannes Schulz", 11 | "email": "hannes.schulz@aboutcoders.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.0.0", 16 | "symfony/symfony": "~2.6|~3.0|~4.0", 17 | "myclabs/php-enum": "~1.5", 18 | "gedmo/doctrine-extensions": "~2.0", 19 | "jms/serializer-bundle": "*", 20 | "nelmio/api-doc-bundle": "~3", 21 | "doctrine/annotations": "1.*", 22 | "aboutcoders/process-control": "~1.3", 23 | "aboutcoders/scheduler-bundle": "~1.2", 24 | "aboutcoders/resource-lock-bundle": "~0.1" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "~5.0|~6.0", 28 | "php-mock/php-mock-phpunit": "dev-master", 29 | "mikey179/vfsStream": "^1.5", 30 | "doctrine/orm": "~2.2,>=2.2.3", 31 | "doctrine/doctrine-bundle": "~1.2", 32 | "symfony/monolog-bundle": "~2", 33 | "symfony/swiftmailer-bundle": "~2.3", 34 | "sonata-project/notification-bundle": "~2.2", 35 | "zendframework/zendxml": "~1", 36 | "bernard/bernard-bundle" : "dev-master", 37 | "aboutcoders/process-control-bundle": "~1.3@dev" 38 | }, 39 | "suggest": { 40 | "aboutcoders/process-control-bundle": "Adds process control, requires ^1.3", 41 | "aboutcoders/supervisor-bundle": "Manage supervisor processes, requires dev-master" 42 | }, 43 | "autoload": { 44 | "psr-4": { "Abc\\Bundle\\JobBundle\\": "" } 45 | }, 46 | "minimum-stability": "dev", 47 | "prefer-stable": true 48 | } 49 | -------------------------------------------------------------------------------- /Validator/Job/ConstraintProviderInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Validator\Job; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface ConstraintProviderInterface 17 | { 18 | /** 19 | * Returns the priority of the provider, which determines which provider is used for a job type. 20 | * 21 | * @return int Priority number (higher - preferred) 22 | */ 23 | public function getPriority(); 24 | 25 | /** 26 | * Returns an array of constraints to validate the parameters of a job. 27 | * 28 | * The number of the elements in the array should match the number of parameters a job can be created or updated with. 29 | * 30 | * Runtime parameters are not validated! 31 | * 32 | * Assuming the job does not use any runtime parameters the first element will be used to validate the first parameter of 33 | * the job, the seconds element will be used to validate the seconds parameter and so on. Use null to prevent validation 34 | * of a parameter. 35 | * 36 | * If your job uses runtime parameters they must be omitted in the returned constraints. If the method signature defines 37 | * three parameters where second parameter is a runtime parameter the returned array should only contain two elements, 38 | * where first element is used to validate first parameter and second element is used to validate third parameter. 39 | * 40 | * If the array contains less elements that the job defines parameters the remaining parameters will not be validated. 41 | * 42 | * @param string $type The job type 43 | * @return array The constraints of a job type 44 | */ 45 | public function getConstraints($type); 46 | } -------------------------------------------------------------------------------- /Logger/Handler/OrmHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Model\LogInterface; 14 | use Abc\Bundle\JobBundle\Model\LogManagerInterface; 15 | use Monolog\Handler\AbstractProcessingHandler; 16 | use Monolog\Logger; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class OrmHandler extends AbstractProcessingHandler 22 | { 23 | /** 24 | * @var LogManagerInterface 25 | */ 26 | protected $manager; 27 | 28 | /** 29 | * @param LogManagerInterface $manager 30 | * @param bool|int $level defaults to Monolog\Logger\Logger::DEBUG 31 | * @param bool $bubble defaults to true 32 | */ 33 | public function __construct(LogManagerInterface $manager, $level = Logger::DEBUG, $bubble = true) 34 | { 35 | parent::__construct($level, $bubble); 36 | 37 | $this->manager = $manager; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | protected function write(array $record) 44 | { 45 | $log = $this->manager->create(); 46 | 47 | $this->populateLog($log, $record); 48 | 49 | $this->manager->save($log); 50 | } 51 | 52 | /** 53 | * @param LogInterface $log 54 | * @param $record 55 | */ 56 | protected function populateLog(LogInterface $log, $record) 57 | { 58 | $log->setChannel($record['channel']); 59 | $log->setLevel($record['level']); 60 | $log->setLevelName($record['level_name']); 61 | $log->setMessage($record['message']); 62 | $log->setDatetime($record['datetime']); 63 | $log->setContext($record['context']); 64 | $log->setExtra($record['extra']); 65 | } 66 | } -------------------------------------------------------------------------------- /Adapter/Bernard/ControlledConsumer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Adapter\Bernard; 12 | 13 | use Abc\ProcessControl\ControllerInterface; 14 | use Bernard\Consumer as BaseConsumer; 15 | use Bernard\Queue; 16 | use Bernard\Router; 17 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 18 | 19 | /** 20 | * A custom implementation of Consumer that is controlled by a process controller. 21 | * 22 | * @author Hannes Schulz 23 | */ 24 | class ControlledConsumer extends BaseConsumer 25 | { 26 | /** 27 | * @var ControllerInterface 28 | */ 29 | private $controller; 30 | 31 | /** 32 | * @param Router $router 33 | * @param EventDispatcherInterface $dispatcher 34 | * @param ControllerInterface $controller 35 | */ 36 | public function __construct(Router $router, EventDispatcherInterface $dispatcher, ControllerInterface $controller) 37 | { 38 | parent::__construct($router, $dispatcher); 39 | 40 | $this->controller = $controller; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function tick(Queue $queue, array $options = []) 47 | { 48 | // weired, no clue why this is necessary, but somehow configure is not invoked otherwise 49 | $this->doConfigure($options); 50 | 51 | if ($this->controller->doStop()) { 52 | return false; 53 | } 54 | 55 | if ($this->controller->doPause()) { 56 | return true; 57 | } 58 | 59 | return parent::tick($queue, $options); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | protected function doConfigure(array $options) 66 | { 67 | parent::configure($options); 68 | } 69 | } -------------------------------------------------------------------------------- /Listener/JobListener.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Listener; 12 | 13 | use Abc\Bundle\JobBundle\Event\ExecutionEvent; 14 | use Abc\Bundle\JobBundle\Job\ManagerInterface; 15 | use Abc\Bundle\JobBundle\Logger\LoggerFactoryInterface; 16 | use Psr\Log\LoggerInterface; 17 | use Psr\Log\NullLogger; 18 | 19 | /** 20 | * Registers the default runtime parameters "manager" and "logger". 21 | * 22 | * @author Hannes Schulz 23 | */ 24 | class JobListener 25 | { 26 | /** 27 | * @var ManagerInterface 28 | */ 29 | private $manager; 30 | 31 | /** 32 | * @var LoggerFactoryInterface 33 | */ 34 | private $factory; 35 | 36 | /** 37 | * @var LoggerInterface 38 | */ 39 | private $logger; 40 | 41 | /** 42 | * @param ManagerInterface $manager 43 | * @param LoggerFactoryInterface $factory 44 | */ 45 | function __construct(ManagerInterface $manager, LoggerFactoryInterface $factory, LoggerInterface $logger = null) 46 | { 47 | $this->manager = $manager; 48 | $this->factory = $factory; 49 | $this->logger = $logger == null ? new NullLogger() : $logger; 50 | } 51 | 52 | /** 53 | * @param ExecutionEvent $event 54 | * @return void 55 | */ 56 | public function onPreExecute(ExecutionEvent $event) 57 | { 58 | $event->getContext()->set('abc.manager', $this->manager); 59 | 60 | $this->logger->debug('Added runtime parameter "manager" to context', ['abc.manager' => $this->manager]); 61 | 62 | $logger = $this->factory->create($event->getJob()); 63 | 64 | $event->getContext()->set('abc.logger', $logger); 65 | 66 | $this->logger->debug('Added runtime parameter "logger" to context', ['abc.logger' => $logger]); 67 | } 68 | } -------------------------------------------------------------------------------- /Controller/BaseController.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Controller; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobTypeRegistry; 14 | use Abc\Bundle\JobBundle\Job\ManagerInterface; 15 | use Abc\Bundle\JobBundle\Model\JobManagerInterface; 16 | use Abc\Bundle\JobBundle\Serializer\SerializerInterface; 17 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 18 | use Symfony\Component\HttpFoundation\Response; 19 | use Symfony\Component\Validator\Validator\ValidatorInterface; 20 | 21 | /** 22 | * @author Hannes Schulz 23 | */ 24 | abstract class BaseController extends Controller 25 | { 26 | /** 27 | * @param mixed $data 28 | * @param int $status 29 | * @return Response 30 | */ 31 | protected function serialize($data, $status = 200) 32 | { 33 | return new Response($this->getSerializer()->serialize($data, 'json'), $status); 34 | } 35 | 36 | /** 37 | * @return JobTypeRegistry 38 | */ 39 | protected function getRegistry() 40 | { 41 | return $this->get('abc.job.registry'); 42 | } 43 | 44 | /** 45 | * @return ManagerInterface 46 | */ 47 | protected function getManager() 48 | { 49 | return $this->get('abc.job.manager'); 50 | } 51 | 52 | /** 53 | * @return JobManagerInterface 54 | */ 55 | protected function getJobManager() 56 | { 57 | return $this->get('abc.job.job_manager'); 58 | } 59 | 60 | /** 61 | * @return SerializerInterface 62 | */ 63 | protected function getSerializer() 64 | { 65 | return $this->get('abc.job.serializer'); 66 | } 67 | 68 | /** 69 | * @return ValidatorInterface 70 | */ 71 | protected function getValidator() 72 | { 73 | return $this->get('abc.job.validator'); 74 | } 75 | } -------------------------------------------------------------------------------- /Resources/config/services/adapter_sonata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | %abc.job.default_queue% 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Job/Context/Context.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Context; 12 | 13 | use Abc\Bundle\JobBundle\Job\Context\Exception\ParameterNotFoundException; 14 | 15 | /** 16 | * @author Hannes Schulz 17 | */ 18 | class Context implements ContextInterface 19 | { 20 | 21 | protected $parameters = array(); 22 | 23 | /** 24 | * @param array $parameters An array of parameters 25 | */ 26 | public function __construct(array $parameters = array()) 27 | { 28 | foreach($parameters as $name => $parameter) 29 | { 30 | $this->set($name, $parameter); 31 | } 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function clear() 38 | { 39 | $this->parameters = array(); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function all() 46 | { 47 | return $this->parameters; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function get($name) 54 | { 55 | $name = strtolower($name); 56 | if(!array_key_exists($name, $this->parameters)) 57 | { 58 | throw new ParameterNotFoundException($name); 59 | } 60 | 61 | return $this->parameters[$name]; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function set($name, $value) 68 | { 69 | $this->parameters[strtolower($name)] = $value; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function has($name) 76 | { 77 | return array_key_exists(strtolower($name), $this->parameters); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function remove($name) 84 | { 85 | unset($this->parameters[strtolower($name)]); 86 | } 87 | } -------------------------------------------------------------------------------- /Resources/docs/logging.md: -------------------------------------------------------------------------------- 1 | Logging 2 | ======= 3 | 4 | During the execution of a job each job has access to it's own standard PSR logger. 5 | 6 | There are two ways to inject the logger into the job class. 7 | 8 | ## Injecting the logger as a runtime parameter 9 | 10 | To inject the logger as a runtime parameter you simply have to specify the `@abc.logger` in the `@ParamType` annotation of the method and add the logger to the method signature: 11 | 12 | ```php 13 | namespace My\Bundle\ExampleBundle\Job\MyJob; 14 | 15 | class MyJob 16 | { 17 | /** 18 | * @ParamType("logger", type="@abc.logger") 19 | */ 20 | public function doSomething(Psr\Log\LoggerInterface $logger) 21 | { 22 | $logger->info('Hello World'); 23 | } 24 | } 25 | ``` 26 | 27 | This approach is especially useful if your job class defines more than one job. 28 | 29 | ## Injecting the logger using the LoggerAwareInterface 30 | 31 | Another option is to inject the logger by making the job class implement the interface `Psr\Log\LoggerInterfaceInterface`: 32 | 33 | ```php 34 | namespace My\Bundle\ExampleBundle\Job\MyJob; 35 | 36 | use Psr\Log\LoggerAwareInterface; 37 | use Psr\Log\LoggerInterface; 38 | 39 | class MyJob implements LoggerAwareInterface 40 | { 41 | protected $logger; 42 | 43 | public function setLogger(LoggerInterface $logger) 44 | { 45 | $this->logger = $logger; 46 | } 47 | 48 | public function doSomething() 49 | { 50 | $this->logger->info('Hello World'); 51 | } 52 | } 53 | ``` 54 | 55 | ## Getting the logs of a job 56 | 57 | Use the following command to get to logs of a job: 58 | 59 | ```php 60 | $records = $manager->getLogs($job->getTicket()); 61 | ``` 62 | 63 | This will return an array of log records that are available for this job. 64 | 65 | __Note:__ Depending on the configuration the records are internally stored either on the filesystem in the JSON format or in the database. 66 | 67 | Please refer to the chapter [Configuration](./configuration.md) to get information about the available configuration options. 68 | 69 | Back to [index](../../README.md) -------------------------------------------------------------------------------- /Listener/ScheduleListener.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Listener; 12 | 13 | use Abc\Bundle\JobBundle\Job\Queue\Message; 14 | use Abc\Bundle\JobBundle\Job\Queue\ProducerInterface; 15 | use Abc\Bundle\JobBundle\Model\Schedule; 16 | use Abc\Bundle\SchedulerBundle\Event\SchedulerEvent; 17 | use Psr\Log\LoggerInterface; 18 | use Psr\Log\NullLogger; 19 | 20 | /** 21 | * Listens to scheduler events for jobs and sends messages to the queue engine 22 | * 23 | * @author Hannes Schulz 24 | */ 25 | class ScheduleListener 26 | { 27 | /** 28 | * @var ProducerInterface 29 | */ 30 | private $queueEngine; 31 | 32 | /** 33 | * @var LoggerInterface 34 | */ 35 | private $logger; 36 | 37 | /** 38 | * @param ProducerInterface $queueEngine 39 | * @param LoggerInterface|null $logger 40 | */ 41 | function __construct(ProducerInterface $queueEngine, LoggerInterface $logger = null) 42 | { 43 | $this->queueEngine = $queueEngine; 44 | $this->logger = $logger == null ? new NullLogger() : $logger; 45 | } 46 | 47 | /** 48 | * @param SchedulerEvent $event 49 | */ 50 | public function onSchedule(SchedulerEvent $event) 51 | { 52 | $schedule = $event->getSchedule(); 53 | if($schedule instanceof Schedule) 54 | { 55 | $this->logger->debug('Process schedule {schedule}', array('schedule' => $schedule)); 56 | 57 | if($job = $schedule->getJob()) 58 | { 59 | $message = new Message($job->getType(), $job->getTicket(), $job->getTicket()); 60 | 61 | $this->queueEngine->produce($message); 62 | 63 | return; 64 | } 65 | 66 | $this->logger->error('There is no job associated with this schedule {schedule}', array('schedule' => $schedule)); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AbcJobBundle 2 | ============ 3 | 4 | A symfony bundle to process jobs asynchronously by simply annotating a method and registering the class within the service container. 5 | 6 | Build Status: [![Build Status](https://travis-ci.org/aboutcoders/job-bundle.svg?branch=master)](https://travis-ci.org/aboutcoders/job-bundle) 7 | 8 | ## Features 9 | 10 | This bundle provides the following features: 11 | 12 | - Asynchronous execution of jobs 13 | - Status information about jobs 14 | - Functionality to cancel, update, restart a job 15 | - Repeated execution of jobs with schedules (cron based expressions) 16 | - JSON REST-Api 17 | - Support for multiple message queue systems: 18 | - Doctrine DBAL 19 | - PhpAmqp / RabbitMQ 20 | - InMemory 21 | - Predis / PhpRedis 22 | - Amazon SQS 23 | - Iron MQ 24 | - Pheanstalk 25 | 26 | ## Documentation 27 | 28 | - [Installation](./Resources/docs/installation.md) 29 | - [Configuration](./Resources/docs/configuration.md) 30 | - [Basic Usage](./Resources/docs/basic-usage.md) 31 | - [Message Consuming](./Resources/docs/message-consuming.md) 32 | - [Job Management](./Resources/docs/job-management.md) 33 | - [Scheduled Jobs](./Resources/docs/scheduled-jobs.md) 34 | - [Cancel Jobs](./Resources/docs/cancel-jobs.md) 35 | - [Runtime Parameters](./Resources/docs/runtime-parameters.md) 36 | - [Serialization](./Resources/docs/serialization.md) 37 | - [Validation](./Resources/docs/validation.md) 38 | - [Logging](./Resources/docs/logging.md) 39 | - [Lifecycle Events](./Resources/docs/lifecycle-events.md) 40 | - [Unit Testing](./Resources/docs/unit-testing.md) 41 | - [REST-API](./Resources/docs/rest-api.md) 42 | - [Process Control](./Resources/docs/process-control.md) 43 | - [Clustered Environment](./Resources/docs/clustered-environment.md) 44 | - [Configuration Reference](./Resources/docs/configuration-reference.md) 45 | 46 | ## Demo Project 47 | 48 | Please take a look at [aboutcoders/job-bundle-skeleton-app](https://github.com/aboutcoders/job-bundle-skeleton-app) to see how the AbcJobBundle can be used within Symfony project. 49 | 50 | ## License 51 | 52 | The MIT License (MIT). Please see [License File](./LICENSE) for more information. -------------------------------------------------------------------------------- /Tests/Functional/Doctrine/LogManagerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Functional\Doctrine; 12 | 13 | use Abc\Bundle\JobBundle\Doctrine\LogManager; 14 | use Abc\Bundle\JobBundle\Entity\Log; 15 | use Abc\Bundle\JobBundle\Test\DatabaseKernelTestCase; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class LogManagerTest extends DatabaseKernelTestCase 21 | { 22 | /** 23 | * @var LogManager 24 | */ 25 | private $subject; 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public function setUp() 31 | { 32 | parent::setUp(); 33 | 34 | $this->subject = new \Abc\Bundle\JobBundle\Entity\LogManager( 35 | $this->getEntityManager(), 36 | 'Abc\Bundle\JobBundle\Logger\Entity\Log' 37 | ); 38 | } 39 | 40 | public function testIsExpectedInstance() 41 | { 42 | $this->assertInstanceOf(LogManager::class, $this->subject); 43 | } 44 | 45 | public function testCRUD() 46 | { 47 | $log = $this->subject->create(); 48 | $log->setChannel('Channel'); 49 | $log->setLevel(200); 50 | $log->setLevelName('info'); 51 | $log->setMessage('Message'); 52 | $log->setDatetime(new \DateTime()); 53 | $log->setContext(['context' => 'Context']); 54 | $log->setExtra(['extra' => 'Extra']); 55 | 56 | $this->subject->save($log); 57 | 58 | $this->getEntityManager()->clear(); 59 | 60 | /** @var Log[] $logs */ 61 | $logs = $this->subject->findAll(); 62 | /** @var Log $log */ 63 | $log = $logs[0]; 64 | 65 | $this->assertCount(1, $logs); 66 | $this->assertEquals($log, $logs[0]); 67 | 68 | $this->subject->delete($log); 69 | 70 | $this->getEntityManager()->clear(); 71 | 72 | $this->assertEmpty($this->subject->findAll()); 73 | } 74 | } -------------------------------------------------------------------------------- /Job/JobBuilder.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job; 12 | 13 | use Abc\Bundle\JobBundle\Model\Job; 14 | use Abc\Bundle\JobBundle\Model\Schedule; 15 | 16 | /** 17 | * @author Hannes Schulz 18 | */ 19 | class JobBuilder 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $type; 25 | 26 | /** 27 | * @var array 28 | */ 29 | protected $parameters; 30 | 31 | /** 32 | * @var array 33 | */ 34 | protected $schedules = []; 35 | 36 | /** 37 | * @param string $type the job type 38 | */ 39 | protected function __construct($type) 40 | { 41 | $this->type = $type; 42 | } 43 | 44 | /** 45 | * @param $type 46 | * @return static 47 | */ 48 | public static function create($type) 49 | { 50 | return new static($type); 51 | } 52 | 53 | /** 54 | * @param array $parameters 55 | * @return $this The current instance 56 | */ 57 | public function setParameters(array $parameters) 58 | { 59 | $this->parameters = $parameters; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @param string $type The scheduler type 66 | * @param string $expression The scheduler expression 67 | * @return $this The current instance 68 | */ 69 | public function addSchedule($type, $expression) 70 | { 71 | $this->schedules[] = [$type, $expression]; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @return JobInterface 78 | */ 79 | public function build() 80 | { 81 | $job = new Job(); 82 | $job->setType($this->type); 83 | $job->setParameters($this->parameters); 84 | foreach ($this->schedules as $schedule) { 85 | $job->addSchedule(new Schedule($schedule[0], $schedule[1])); 86 | } 87 | 88 | return $job; 89 | } 90 | } -------------------------------------------------------------------------------- /Model/LogInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Model; 12 | 13 | /** 14 | * @author Hannes Schulz 15 | */ 16 | interface LogInterface 17 | { 18 | /** 19 | * @return string 20 | */ 21 | public function getChannel(); 22 | 23 | /** 24 | * @param string|null $channel 25 | * @return void 26 | */ 27 | public function setChannel($channel); 28 | 29 | /** 30 | * @return int 31 | */ 32 | public function getLevel(); 33 | 34 | /** 35 | * @param int $level 36 | * @return void 37 | */ 38 | public function setLevel($level); 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getLevelName(); 44 | 45 | /** 46 | * @param string $levelName 47 | * @return void 48 | */ 49 | public function setLevelName($levelName); 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function getMessage(); 55 | 56 | /** 57 | * @param string|null $message 58 | * @return void 59 | */ 60 | public function setMessage($message); 61 | 62 | /** 63 | * @return \DateTime 64 | */ 65 | public function getDatetime(); 66 | 67 | /** 68 | * @param \DateTime $datetime 69 | * @return void 70 | */ 71 | public function setDatetime($datetime); 72 | 73 | /** 74 | * @return array 75 | */ 76 | public function getContext(); 77 | 78 | /** 79 | * @param array|null $context 80 | * @return void 81 | */ 82 | public function setContext($context); 83 | 84 | /** 85 | * @return array 86 | */ 87 | public function getExtra(); 88 | 89 | /** 90 | * @param array|null $extra 91 | * @return void 92 | */ 93 | public function setExtra($extra); 94 | 95 | /** 96 | * @return array The PSR compliant log record 97 | */ 98 | public function toRecord(); 99 | } -------------------------------------------------------------------------------- /Resources/config/routing/rest-job.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | AbcJobBundle:Job:list 9 | json 10 | json 11 | 12 | 13 | 14 | AbcJobBundle:Job:get 15 | json 16 | json 17 | 18 | 19 | 20 | AbcJobBundle:Job:add 21 | json 22 | json 23 | 24 | 25 | 26 | AbcJobBundle:Job:update 27 | json 28 | json 29 | 30 | 31 | 32 | AbcJobBundle:Job:cancel 33 | json 34 | json 35 | 36 | 37 | 38 | AbcJobBundle:Job:restart 39 | json 40 | json 41 | 42 | 43 | 44 | AbcJobBundle:Job:logs 45 | json 46 | json 47 | 48 | 49 | -------------------------------------------------------------------------------- /Job/Mailer/Mailer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Job\Mailer; 12 | 13 | use Abc\Bundle\JobBundle\Job\JobAwareInterface; 14 | use Abc\Bundle\JobBundle\Annotation\ParamType; 15 | use Abc\Bundle\JobBundle\Job\JobInterface; 16 | use Psr\Log\LoggerInterface; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class Mailer implements JobAwareInterface 22 | { 23 | /** 24 | * @var JobInterface 25 | */ 26 | private $job; 27 | 28 | /** 29 | * @var \Swift_Mailer 30 | */ 31 | private $mailer; 32 | 33 | /** 34 | * @param \Swift_Mailer $mailer 35 | */ 36 | public function __construct(\Swift_Mailer $mailer) 37 | { 38 | $this->mailer = $mailer; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function setJob(JobInterface $job) 45 | { 46 | $this->job = $job; 47 | } 48 | 49 | /** 50 | * Sends a mail. 51 | * 52 | * @ParamType("message", type="Abc\Bundle\JobBundle\Job\Mailer\Message") 53 | * @ParamType("logger", type="@abc.logger") 54 | * 55 | * @param Message $message 56 | * @param LoggerInterface $logger 57 | * @throws \Exception Rethrows exceptions thrown by mailer 58 | */ 59 | public function send(Message $message, LoggerInterface $logger) 60 | { 61 | $logger->debug('Send mail {message}', array('message' => $message)); 62 | 63 | $mail = $this->mailer->createMessage() 64 | ->setSubject($message->getSubject()) 65 | ->setFrom($message->getFrom()) 66 | ->setTo($message->getTo()); 67 | 68 | $mail->addPart($message->getMessage(), 'text/plain'); 69 | 70 | try { 71 | $this->mailer->send($mail); 72 | 73 | $this->mailer->getTransport()->stop(); 74 | } catch (\Exception $e) { 75 | $this->mailer->getTransport()->stop(); 76 | 77 | throw $e; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Test/DatabaseWebTestCase.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Test; 12 | 13 | use Symfony\Bundle\FrameworkBundle\Console\Application; 14 | use Symfony\Component\Console\Input\ArrayInput; 15 | use Symfony\Component\DependencyInjection\ContainerInterface; 16 | 17 | /** 18 | * @author Hannes Schulz 19 | */ 20 | class DatabaseWebTestCase extends WebTestCase 21 | { 22 | /** 23 | * @var Application 24 | */ 25 | protected static $application; 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | protected function setUp() 31 | { 32 | self::runCommand('doctrine:schema:drop', array("--force" => true)); 33 | self::runCommand('doctrine:schema:update', array("--force" => true)); 34 | } 35 | 36 | /** 37 | * @param string $command 38 | * @param array $options 39 | * @return int 0 if everything went fine, or an error code 40 | * @throws \Exception 41 | */ 42 | protected static function runCommand($command, array $options = array()) 43 | { 44 | $options["-e"] = "test"; 45 | $options["-q"] = null; 46 | $options = array_merge($options, array('command' => $command)); 47 | 48 | return self::getApplication()->run(new ArrayInput($options)); 49 | } 50 | 51 | /** 52 | * @return Application 53 | */ 54 | protected static function getApplication() 55 | { 56 | if(null === self::$application) 57 | { 58 | $client = static::createClient(); 59 | 60 | self::$application = new Application($client->getKernel()); 61 | self::$application->setAutoExit(false); 62 | self::$application->setCatchExceptions(false); 63 | } 64 | 65 | return self::$application; 66 | } 67 | 68 | 69 | /** 70 | * @return ContainerInterface 71 | */ 72 | protected function getContainer() 73 | { 74 | return static::$kernel->getContainer(); 75 | } 76 | } -------------------------------------------------------------------------------- /Tests/Logger/Handler/BaseHandlerFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Abc\Bundle\JobBundle\Tests\Logger\Handler; 12 | 13 | use Abc\Bundle\JobBundle\Logger\Handler\BaseHandlerFactory; 14 | use Monolog\Formatter\FormatterInterface; 15 | use Monolog\Handler\HandlerInterface; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | /** 19 | * @author Hannes Schulz 20 | */ 21 | class BaseHandlerFactoryTest extends TestCase 22 | { 23 | /** 24 | * @var BaseHandlerFactory 25 | */ 26 | private $subject; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function setUp() 32 | { 33 | $this->subject = $this->getMockForAbstractClass(BaseHandlerFactory::class); 34 | } 35 | 36 | public function testInitHandler() { 37 | 38 | $handler = $this->createMock(HandlerInterface::class); 39 | $formatter = $this->createMock(FormatterInterface::class); 40 | $processors = ['foobar']; 41 | 42 | $this->subject->setFormatter($formatter); 43 | $this->subject->setProcessors($processors); 44 | 45 | $handler->expects($this->once()) 46 | ->method('setFormatter') 47 | ->with($formatter); 48 | 49 | $handler->expects($this->once()) 50 | ->method('pushProcessor') 51 | ->with('foobar'); 52 | 53 | $this->invokeMethod($this->subject, 'initHandler', [$handler]); 54 | } 55 | 56 | /** 57 | * Call protected/private method of a class. 58 | * 59 | * @param object &$object 60 | * @param string $methodName 61 | * @param array $parameters 62 | * @return mixed 63 | */ 64 | private function invokeMethod(&$object, $methodName, array $parameters = array()) 65 | { 66 | $reflection = new \ReflectionClass(get_class($object)); 67 | $method = $reflection->getMethod($methodName); 68 | $method->setAccessible(true); 69 | 70 | return $method->invokeArgs($object, $parameters); 71 | } 72 | } --------------------------------------------------------------------------------