<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Controller;

use Abc\Bundle\JobBundle\Api\BadRequestResponse;
use Abc\Bundle\JobBundle\Api\ParameterConstraintViolation;
use Abc\Bundle\JobBundle\Job\Exception\TicketNotFoundException;
use Abc\Bundle\JobBundle\Job\Exception\ValidationFailedException;
use Abc\Bundle\JobBundle\Job\JobInterface;
use Abc\Bundle\JobBundle\Job\Status;
use Abc\Bundle\JobBundle\Model\Job;
use Abc\Bundle\JobBundle\Serializer\DeserializationContext;
use Abc\Bundle\JobBundle\Validator\Constraints as AbcAssert;
use Abc\Bundle\JobBundle\Model\JobList;
use Nelmio\ApiDocBundle\Annotation\Operation;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class JobController extends BaseController
{
    /**
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Returns a collection of jobs",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful",
     *         @SWG\Schema(
     *              type="array",
     *              @Model(type=Abc\Bundle\JobBundle\Model\JobList::class)
     *         )
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Returned when request is invalid",
     *         @Model(type=Abc\Bundle\JobBundle\Api\BadRequestResponse::class)
     *     ),
     *     @SWG\Parameter(
     *         name="page",
     *         in="query",
     *         type="integer",
     *         default="1",
     *         format="\d+",
     *         required=false,
     *         description="The page number of the result set"
     *     ),
     *     @SWG\Parameter(
     *         name="limit",
     *         in="query",
     *         type="integer",
     *         default="10",
     *         format="\d+",
     *         required=false,
     *         description="The page size"
     *     ),
     *     @SWG\Parameter(
     *         name="sortCol",
     *         in="query",
     *         type="string",
     *         default="createdAt",
     *         format="(ticket|type|status|createdAt|terminatedAt)",
     *         required=false,
     *         description="The sort column"
     *     ),
     *     @SWG\Parameter(
     *         name="sortDir",
     *         in="query",
     *         type="string",
     *         default="DESC",
     *         format="(ASC|DESC)",
     *         required=false,
     *         description="The sort direction"
     *     ),
     *     @SWG\Parameter(
     *         name="criteria",
     *         in="query",
     *         type="array",
     *         @SWG\Items(
     *             type="string"
     *         ),
     *         required=false,
     *         description="The search criteria defined as associative array, valid keys are ticket|type|status"
     *     )
     * )
     *
     * @param Request $request
     * @return Response
     */
    public function listAction(Request $request)
    {
        $criteria   = $request->query->get('criteria', array());
        $page       = $request->query->get('page', 1);
        $sortColumn = $request->query->get('sortCol', 'createdAt');
        $sortDir    = $request->query->get('sortDir', 'DESC');
        $limit      = $request->query->get('limit', 10);

        if ($errors = $this->validateQueryParameters($page, $sortColumn, $sortDir, $limit, $criteria)) {

            $response = new BadRequestResponse('Invalid query parameters', 'One or more query parameters are invalid');
            $response->setErrors($errors);

            return $this->serialize($response, 400);
        }

        $page   = (int)$page - 1;
        $offset = ($page > 0) ? ($page) * $limit : 0;

        $criteria = $this->filterCriteria($criteria);

        $manager = $this->getJobManager();

        $entities = $manager->findBy($criteria, [$sortColumn => $sortDir], $limit, $offset);
        $count    = $manager->findByCount($criteria);

        $list = new JobList();
        $list->setItems($entities);
        $list->setTotalCount($count);

        return $this->serialize($list);
    }

    /**
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Returns a job",
     *     @SWG\Response(
     *         response="201",
     *         description="Returned when successful"
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Returned when node not found"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param string $ticket
     * @return Response
     */
    public function getAction($ticket)
    {
        try {
            return $this->serialize($this->getManager()->get($ticket));
        } catch (TicketNotFoundException $e) {
            return $this->createNotFoundResponse($e->getMessage());
        }
    }

    /**
     * Adds a new job.
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Adds a job",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful"
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Form validation error"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param Request $request
     * @return Response
     */
    public function addAction(Request $request)
    {
        $job = $this->deserializeJob($request);

        if ($response = $this->validateJob($job)) {
            return $this->serialize($response, 400);
        }

        try {
            return $this->serialize($this->getManager()->add($job));
        } catch (ValidationFailedException $e) {
            $response = new BadRequestResponse('Invalid request', 'The request contains invalid job parameters');
            $response->setErrors($e->getConstraintViolationList());

            return $this->serialize($response, 400);
        }
    }

    /**
     * Updates a job.
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Updates a job",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful",
     *         @Model(type=Abc\Bundle\JobBundle\Model\Job::class)
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Form validation error"
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Returned when job not found"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param Request $request
     * @return Response
     */
    public function updateAction(Request $request)
    {
        $job = $this->deserializeJob($request);

        if ($response = $this->validateJob($job)) {
            return $this->serialize($response, 400);
        }

        try {
            return $this->serialize($this->getManager()->update($job));
        } catch (ValidationFailedException $e) {
            $response = new BadRequestResponse('Invalid request', 'The request contains invalid job parameters');
            $response->setErrors($e->getConstraintViolationList());

            return $this->serialize($response, 400);
        }
    }

    /**
     * Cancels a job.
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Cancels a job",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful",
     *         @Model(type=Abc\Bundle\JobBundle\Model\JobList::class)
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Returned when job not found"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     ),
     *     @SWG\Parameter(
     *         name="force",
     *         in="query",
     *         type="boolean",
     *         default="false",
     *         required=false,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param string $ticket
     * @param bool   $force Whether to force cancellation (false by default)
     * @return Response
     */
    public function cancelAction($ticket, $force = false)
    {
        try {
            return $this->serialize($this->getManager()->cancel($ticket, $force));
        } catch (TicketNotFoundException $e) {
            return $this->createNotFoundResponse($e->getMessage());
        }
    }

    /**
     * Restarts a job.
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Restarts a job",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful",
     *         @Model(type=Abc\Bundle\JobBundle\Model\JobList::class)
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Returned when job not found"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param string $ticket
     * @return Response
     */
    public function restartAction($ticket)
    {
        try {
            return $this->serialize($this->getManager()->restart($ticket));
        } catch (TicketNotFoundException $e) {
            return $this->createNotFoundResponse($e->getMessage());
        }
    }

    /**
     * Returns the logs of a job.
     * @Operation(
     *     tags={"AbcJobBundle"},
     *     summary="Returns the logs of a job",
     *     @SWG\Response(
     *         response="200",
     *         description="Returned when successful",
     *         @SWG\Schema(
     *              type="array",
     *              @Model(type=Abc\Bundle\JobBundle\Model\Log::class)
     *         )
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Returned when job not found"
     *     ),
     *     @SWG\Parameter(
     *         name="ticket",
     *         in="query",
     *         type="string",
     *         required=true,
     *         description="The job ticket"
     *     )
     * )
     *
     * @param string $ticket
     * @return Response
     */
    public function logsAction($ticket)
    {
        try {
            return $this->serialize($this->getManager()->getLogs($ticket));
        } catch (TicketNotFoundException $e) {
            return $this->createNotFoundResponse($e->getMessage());
        }
    }

    /**
     * @param Request $request
     * @param array   $groups
     * @return JobInterface|mixed
     */
    protected function deserializeJob(Request $request, array $groups = [])
    {
        $context = null;
        if (count($groups) > 0) {
            $context = new DeserializationContext();
            $context->setGroups($groups);
        }

        return $this->getSerializer()->deserialize(
            json_encode($request->request->all(), true),
            Job::class,
            'json',
            $context
        );
    }

    /**
     * @param $criteria
     * @return array
     * @throws BadRequestHttpException If invalid status value is set in $criteria
     */
    protected function filterCriteria($criteria)
    {
        if (!is_array($criteria)) {
            throw new HttpException(400, 'Invalid search criteria');
        }

        if (isset($criteria['status'])) {
            try {
                $criteria['status'] = new Status($criteria['status']);
            } catch (\Exception $e) {
                throw new BadRequestHttpException('Invalid status defined in criteria');
            }
        }

        return $criteria;
    }

    /**
     * @param mixed $page
     * @param mixed $sortColumn
     * @param mixed $sortDir
     * @param mixed $limit
     * @param mixed $criteria
     * @return array
     */
    private function validateQueryParameters($page, $sortColumn, $sortDir, $limit, $criteria)
    {
        $validationErrors = [];

        $this->validateQueryParameter($validationErrors, 'page', $page, new Assert\Range(['min' => 1]));
        $this->validateQueryParameter($validationErrors, 'sortCol', $sortColumn, new Assert\Choice(['choices' => ['ticket', 'type', 'status', 'createdAt', 'terminatedAt'], 'message' => 'The value should be a valid sort column']));
        $this->validateQueryParameter($validationErrors, 'sortDir', $sortDir, new Assert\Choice(['choices' => ['ASC', 'DESC'], 'message' => 'The value should be a valid sort direction']));
        $this->validateQueryParameter($validationErrors, 'limit', $limit, new Assert\Range(['min' => 1]));
        $this->validateQueryParameter($validationErrors, 'criteria', $criteria, new Assert\Collection([
            'fields'             => [
                'ticket' => new Assert\Uuid(),
                'status' => new AbcAssert\Status(),
                'type'   => new AbcAssert\JobType(),
            ],
            'allowMissingFields' => true
        ]));

        return $validationErrors;
    }

    /**
     * @param array $validationErrors
     * @param       $name
     * @param       $value
     * @param       $constraint
     */
    private function validateQueryParameter(array &$validationErrors, $name, $value, $constraint)
    {
        $errors = $this->getValidator()->validate($value, $constraint);
        if (count($errors) > 0) {
            foreach ($errors as $error) {
                $validationErrors[] = new ParameterConstraintViolation($name, $error->getMessage());
            }
        }
    }

    /**
     * @param $job
     * @return BadRequestResponse|null
     */
    private function validateJob($job)
    {
        if ($this->getParameter('abc.job.rest.validate')) {
            $errors = $this->getValidator()->validate($job);
            if (count($errors) > 0) {
                $response = new BadRequestResponse('Invalid request', 'The request contains invalid job parameters');
                $response->setErrors($errors);

                return $response;
            }
        }

        return null;
    }

    /**
     * @param string $message
     * @return Response
     */
    private function createNotFoundResponse($message)
    {
        return $this->serialize(new BadRequestResponse('Not found', $message), 404);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Job\Context;

use Abc\Bundle\JobBundle\Job\Context\Exception\ParameterNotFoundException;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class Context implements ContextInterface
{

    protected $parameters = array();

    /**
     * @param array $parameters An array of parameters
     */
    public function __construct(array $parameters = array())
    {
        foreach($parameters as $name => $parameter)
        {
            $this->set($name, $parameter);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function clear()
    {
        $this->parameters = array();
    }

    /**
     * {@inheritdoc}
     */
    public function all()
    {
        return $this->parameters;
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        $name = strtolower($name);
        if(!array_key_exists($name, $this->parameters))
        {
            throw new ParameterNotFoundException($name);
        }

        return $this->parameters[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function set($name, $value)
    {
        $this->parameters[strtolower($name)] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function has($name)
    {
        return array_key_exists(strtolower($name), $this->parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function remove($name)
    {
        unset($this->parameters[strtolower($name)]);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Logger\Handler;

use Abc\Bundle\JobBundle\Job\JobInterface;
use Monolog\Handler\HandlerInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class HandlerFactoryRegistry
{
    /**
     * @var array|BaseHandlerFactory[]
     */
    private $factories = [];

    /**
     * @param HandlerFactoryInterface $factory
     */
    public function register(HandlerFactoryInterface $factory)
    {
        $this->factories[] = $factory;
    }

    /**
     * Returns the handlers created by all registered factories.
     *
     * @param JobInterface $job
     * @param int          $level The minimum logging level at which this handler will be triggered
     * @param boolean      $bubble
     * @return array|HandlerInterface[] The created handlers
     */
    public function createHandlers(JobInterface $job, $level, $bubble)
    {
        $handlers = [];
        foreach ($this->factories as $factory) {
            $handlers[] = $factory->createHandler($job, $level, $bubble);
        }

        return $handlers;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\DependencyInjection\Compiler;

use Monolog\Logger;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class RegisterJobsPass implements CompilerPassInterface
{

    /**
     * @var string
     */
    private $registryService;

    /**
     * @var string
     */
    private $jobTag;

    /**
     * Constructor.
     *
     * @param string $registryService Service name of the definition registry in processed container
     * @param string $jobTag          The tag name used for jobs
     */
    public function __construct($registryService = 'abc.job.registry', $jobTag = 'abc.job')
    {
        $this->registryService = $registryService;
        $this->jobTag          = $jobTag;
    }

    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition($this->registryService) && !$container->hasAlias($this->registryService)) {
            return;
        }

        $customLogLevels = $container->getParameter('abc.job.logging.level');
        $registry        = $container->findDefinition('abc.job.registry');

        foreach ($container->findTaggedServiceIds($this->jobTag) as $id => $tags) {
            $def = $container->getDefinition($id);
            if (!$def->isPublic()) {
                throw new \InvalidArgumentException(sprintf('The service "%s" must be public as jobs are lazy-loaded.', $id));
            }

            foreach ($tags as $tag) {
                if (!isset($tag['type'])) {
                    throw new \InvalidArgumentException(sprintf('Service "%s" must define the "type" attribute on "%s" tags.', $id, $this->jobTag));
                }

                if (!isset($tag['method'])) {
                    throw new \InvalidArgumentException(sprintf('Service "%s" must define the "method" attribute on "%s" tags.', $id, $this->jobTag));
                }

                $logLevel  = isset($customLogLevels[$tag['type']]) ? $this->levelToMonologConst($customLogLevels[$tag['type']]) : null;
                $jobTypeId = 'abc.job.type.' . $tag['type'];

                $definition = $this->createType(
                    $id,
                    $tag['type'],
                    array(new Reference($id), $tag['method']),
                    $logLevel
                );

                $container->setDefinition($jobTypeId, $definition);

                $registry->addMethodCall('register', array(new Reference($jobTypeId), true));
            }
        }

        // there as a reason this listener was registered here, what was it?
        if ($container->hasParameter('abc.job.adapter') && $container->getParameter('abc.job.adapter') == 'sonata') {
            $pass = new RegisterSonataListenersPass();
            $pass->process($container);
        }
    }

    /**
     * @param string           $serviceId
     * @param string           $type
     * @param callable         $callable
     * @param string|null      $logLevel
     * @param string|null      $formType
     * @return ChildDefinition
     */
    protected function createType($serviceId, $type, $callable, $logLevel = null, $formType = null)
    {
        $jobType = new ChildDefinition('abc.job.type.prototype');
        $jobType->replaceArgument(0, $serviceId);
        $jobType->replaceArgument(1, $type);
        $jobType->replaceArgument(2, $callable);
        $jobType->replaceArgument(3, $logLevel);

        return $jobType;
    }

    /**
     * @param $level
     * @return int|mixed
     */
    private function levelToMonologConst($level)
    {
        return is_int($level) ? $level : constant(Logger::class . '::' . strtoupper($level));
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Logger;

use Abc\Bundle\JobBundle\Job\JobInterface;
use Abc\Bundle\JobBundle\Job\JobTypeRegistry;
use Abc\Bundle\JobBundle\Logger\Handler\HandlerFactoryRegistry;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Psr\Log\NullLogger;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class LoggerFactory implements LoggerFactoryInterface
{
    /**
     * @var JobTypeRegistry
     */
    protected $registry;

    /**
     * @var HandlerFactoryRegistry
     */
    protected $handlerFactory;

    /**
     * @var int
     */
    protected $level;

    /**
     * @var boolean
     */
    protected $bubble;

    /**
     * @var array|HandlerInterface[]
     */
    protected $handlers = array();

    /**
     * @param JobTypeRegistry        $registry
     * @param HandlerFactoryRegistry $handlerFactory
     * @param int                    $level
     * @param bool                   $bubble
     */
    public function __construct(JobTypeRegistry $registry, HandlerFactoryRegistry $handlerFactory, $level, $bubble)
    {
        $this->registry       = $registry;
        $this->handlerFactory = $handlerFactory;
        $this->level          = $level;
        $this->bubble         = $bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function create(JobInterface $job)
    {
        $level = $this->registry->get($job->getType())->getLogLevel();
        if (false === $level) {
            return new NullLogger();
        } elseif (null === $level) {
            $level = $this->level;
        }

        $handlers = $this->handlerFactory->createHandlers($job, $level, $this->bubble);

        return new Logger($this->buildChannel($job), array_merge($handlers, $this->handlers));
    }

    /**
     * @param HandlerInterface $handler
     * @return void
     */
    public function addHandler(HandlerInterface $handler)
    {
        $this->handlers[] = $handler;
    }

    /**
     * @param JobInterface $job
     * @return string The channel name
     */
    protected function buildChannel(JobInterface $job)
    {
        return $job->getTicket();
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Job\ProcessControl;

use Abc\Bundle\JobBundle\Job\Status;
use Abc\Bundle\JobBundle\Model\JobInterface;
use Abc\Bundle\JobBundle\Model\JobManagerInterface;
use Abc\ProcessControl\ControllerInterface;

/**
 * The default job process controller.
 *
 * This controller uses the configured manager (service-id: abc.job.manager)
 * to refresh the job entity and to check whether the job status is CANCELLED
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class StatusController implements ControllerInterface
{
    /**
     * @var JobManagerInterface
     */
    protected $manager;

    /**
     * @var
     */
    protected $interval;

    /**
     * @var JobInterface
     */
    protected $job;

    /**
     * @var integer
     */
    private $lastCheck;

    /**
     * @param \Abc\Bundle\JobBundle\Job\JobInterface $job
     * @param JobManagerInterface                    $manager
     * @param integer                                $interval The minimum number of seconds that must have been passed between two refresh operations
     * @throws \InvalidArgumentException If interval is not greater than of equal to zero
     */
    public function __construct(\Abc\Bundle\JobBundle\Job\JobInterface $job, JobManagerInterface $manager, $interval)
    {
        if ((int)$interval < 0) {
            throw new \InvalidArgumentException('$interval must be greater than or equal to zero');
        }

        $class = $manager->getClass();
        if (!$job instanceof $class) {
            throw new \InvalidArgumentException('The given $job is not managed by the given $manager, $job must be an instance of' . $class);
        }

        $this->job      = $job;
        $this->manager  = $manager;
        $this->interval = $interval;
    }

    /**
     * {@inheritdoc}
     */
    public function doStop()
    {
        $time = time();
        if (null === $this->lastCheck || (($this->lastCheck + $this->interval) <= $time)) {
            $this->lastCheck = $time;
            $this->manager->refresh($this->job);
        }

        return $this->job->getStatus() == Status::CANCELLING() || $this->job->getStatus() == Status::CANCELLED();
    }

    /**
     * {@inheritdoc}
     */
    public function doExit()
    {
        return $this->doStop();
    }

    /**
     * {@inheritdoc}
     */
    public function doPause()
    {
        return false;
    }

}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Entity;

use Abc\Bundle\JobBundle\Doctrine\Job as BaseJob;
use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class Job extends BaseJob
{
    /**
     * {@inheritdoc}
     */
    public function createSchedule($type, $expression)
    {
        return new Schedule($type, $expression);
    }

    /**
     * {@inheritdoc}
     */
    public function addSchedule(ScheduleInterface $schedule)
    {
        if(!$schedule instanceof Schedule)
        {
            $schedule = new Schedule($schedule->getType(), $schedule->getExpression());
        }

        $schedule->setJob($this);

        parent::addSchedule($schedule);
    }

    /**
     * {@inheritdoc}
     */
    public function removeSchedule(ScheduleInterface $schedule)
    {
        if($schedule instanceof Schedule)
        {
            $schedule->setJob(null);
        }

        parent::removeSchedule($schedule);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\DependencyInjection;

use Monolog\Logger;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class AbcJobExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config        = $this->processConfiguration($configuration, $configs);
        $loader        = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config/services'));

        foreach ($config['service'] as $key => $service) {
            if (null !== $service) {
                $container->setAlias('abc.job.' . $key, $service);
            }
        }

        $container->setParameter('abc.job.controller_service', $config['service']['controller']);
        $container->setParameter('abc.job.locker_service', $config['service']['locker']);

        $this->remapParametersNamespaces(
            $config,
            $container,
            array(
                '' => array(
                    'db_driver'             => 'abc.job.db_driver',
                    'adapter'               => 'abc.job.adapter',
                    'connection'            => 'abc.job.connection',
                    'model_manager_name'    => 'abc.job.model_manager_name',
                    'register_default_jobs' => 'abc.job.register_default_jobs',
                    'queues'                => 'abc.job.queue_config',
                    'default_queue'         => 'abc.job.default_queue',
                )
            )
        );

        $this->remapParametersNamespaces(
            $config,
            $container,
            array(
                'logging' => array(
                    'directory' => 'abc.job.logging.directory',
                )
            )
        );

        $this->remapParametersNamespaces(
            $config,
            $container,
            array(
                'controller' => array(
                    'refresh_interval' => 'abc.job.controller.refresh_interval'
                )
            )
        );

        if ('custom' !== $config['db_driver']) {
            $loader->load(sprintf('%s.xml', $config['db_driver']));
        }

        if ('custom' !== $config['adapter']) {
            $loader->load(sprintf('adapter_%s.xml', $config['adapter']));
        }

        $this->loadManager($config, $loader, $container);
        $this->loadRest($config, $loader, $container);
        $this->loadDefaultJobs($config, $loader, $container);
        $this->loadLogger($config, $loader, $container);

        $loader->load('scheduler.xml');
        $loader->load('validator.xml');
        $loader->load('commands.xml');
        $loader->load('serializer.xml');
        $loader->load('locker.xml');
    }

    /**
     * @param array            $config
     * @param XmlFileLoader    $loader
     * @param ContainerBuilder $container
     */
    private function loadManager(array $config, XmlFileLoader $loader, ContainerBuilder $container)
    {
        $loader->load('registry.xml');
        $loader->load('manager.xml');
        $loader->load('listener.xml');

        $container->getDefinition('abc.job.manager.default')->replaceArgument(8, !$config['manager']['validate'] ? null : new Reference('abc.job.validator'));
    }

    /**
     * @param array            $config
     * @param XmlFileLoader    $loader
     * @param ContainerBuilder $container
     */
    private function loadLogger(array $config, XmlFileLoader $loader, ContainerBuilder $container)
    {
        $storageConfig = $config['logging']['storage_handler'];

        $loader->load('logger.xml');
        $loader->load('logger_storage_' . $storageConfig['type'] . '.xml');

        if ('orm' == $storageConfig['type']) {
            $container->setParameter('abc.job.register_mapping.' . $config['db_driver'], true);
        } elseif ('file' == $storageConfig['type']) {
            $container->setParameter('abc.job.logger.storage.path', $storageConfig['path']);
        }

        $container->setParameter('abc.job.logger.storage.level', $this->levelToMonologConst($storageConfig['level']));
        $container->setParameter('abc.job.logger.storage.bubble', $storageConfig['bubble']);

        $definition = $container->getDefinition('abc.job.logger.storage_handler_factory');
        foreach ($storageConfig['processor'] as $processor) {
            $definition->addMethodCall('setProcessor', [new Reference($processor)]);
        }

        if (isset($config['logging']['stream_handler'])) {
            $loader->load('logger_stream.xml');
            $streamConfig = $config['logging']['stream_handler'];
            $container->setParameter('abc.job.logger.stream.path', $streamConfig['path']);
            $container->setParameter('abc.job.logger.stream.level', $this->levelToMonologConst($streamConfig['level']));
            $container->setParameter('abc.job.logger.stream.bubble', $streamConfig['bubble']);
            if (isset($streamConfig['formatter'])) {
                $container->setAlias('abc.job.logger.stream.formatter', $streamConfig['formatter']);
            }

            $definition = $container->getDefinition('abc.job.logger.stream_handler_factory');
            foreach ($streamConfig['processor'] as $processor) {
                $definition->addMethodCall('setProcessor', [new Reference($processor)]);
            }

            $definition = $container->getDefinition('abc.job.logger.handler_factory_registry');
            $definition->addMethodCall('register', [new Reference('abc.job.logger.stream_handler_factory')]);
        }

        foreach ($config['logging']['handler'] as $handler) {
            $definition = $container->getDefinition('abc.job.logger.factory');
            $definition->addMethodCall('addHandler', [new Reference($handler)]);
        }

        $container->setParameter('abc.job.logging.level', $config['logging']['level']);
    }

    /**
     * @param array            $config
     * @param XmlFileLoader    $loader
     * @param ContainerBuilder $container
     */
    private function loadRest(array $config, XmlFileLoader $loader, ContainerBuilder $container)
    {
        $container->setParameter('abc.job.rest', $config['rest']['enable']);
        $container->setParameter('abc.job.rest.validate', $config['rest']['validate']);
    }

    /**
     * @param array            $config
     * @param XmlFileLoader    $loader
     * @param ContainerBuilder $container
     */
    private function loadDefaultJobs(array $config, XmlFileLoader $loader, ContainerBuilder $container)
    {
        if ($config['register_default_jobs']) {
            $loader->load('default_jobs.xml');
        }
    }

    /**
     * @param array            $config
     * @param ContainerBuilder $container
     * @param array            $map
     * @return void
     */
    private function remapParameters(array $config, ContainerBuilder $container, array $map)
    {
        foreach ($map as $name => $paramName) {
            if (array_key_exists($name, $config)) {
                $container->setParameter($paramName, $config[$name]);
            }
        }
    }

    /**
     * @param array            $config
     * @param ContainerBuilder $container
     * @param array            $namespaces $supportedAdapters
     * @return void
     */
    private function remapParametersNamespaces(array $config, ContainerBuilder $container, array $namespaces)
    {
        foreach ($namespaces as $ns => $map) {
            if ($ns) {
                if (!array_key_exists($ns, $config)) {
                    continue;
                }
                $namespaceConfig = $config[$ns];
            } else {
                $namespaceConfig = $config;
            }
            if (is_array($map)) {
                $this->remapParameters($namespaceConfig, $container, $map);
            } else {
                foreach ($namespaceConfig as $name => $value) {
                    $container->setParameter(sprintf($map, $name), $value);
                }
            }
        }
    }

    /**
     * @param $level
     * @return int|mixed
     */
    private function levelToMonologConst($level)
    {
        return is_int($level) ? $level : constant(Logger::class . '::' . strtoupper($level));
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Abc\Bundle\JobBundle\Job\Status as JobStatus;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class StatusValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if(null === $value) {
            return;
        }

        if(!in_array($value, JobStatus::values())){
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{string}}', $value)
                ->addViolation();
        }
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Adapter\Bernard;

use Abc\ProcessControl\ControllerInterface;
use Bernard\Consumer as BaseConsumer;
use Bernard\Queue;
use Bernard\Router;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * A custom implementation of Consumer that is controlled by a process controller.
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ControlledConsumer extends BaseConsumer
{
    /**
     * @var ControllerInterface
     */
    private $controller;

    /**
     * @param Router                   $router
     * @param EventDispatcherInterface $dispatcher
     * @param ControllerInterface      $controller
     */
    public function __construct(Router $router, EventDispatcherInterface $dispatcher, ControllerInterface $controller)
    {
        parent::__construct($router, $dispatcher);

        $this->controller = $controller;
    }

    /**
     * {@inheritdoc}
     */
    public function tick(Queue $queue, array $options = [])
    {
        // weired, no clue why this is necessary, but somehow configure is not invoked otherwise
        $this->doConfigure($options);

        if ($this->controller->doStop()) {
            return false;
        }

        if ($this->controller->doPause()) {
            return true;
        }

        return parent::tick($queue, $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function doConfigure(array $options)
    {
        parent::configure($options);
    }
}


<?php

declare(strict_types=1);

namespace Chubbyphp\DoctrineDbServiceProvider\ServiceProvider;

use Chubbyphp\DoctrineDbServiceProvider\Logger\DoctrineDbalLogger;
use Chubbyphp\DoctrineDbServiceProvider\Registry\DoctrineDbalConnectionRegistry;
use Doctrine\Common\Cache\ApcuCache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
use Pimple\Container;
use Pimple\ServiceProviderInterface;

/**
 * This provider is heavily inspired by
 * https://github.com/silexphp/Silex-Providers/blob/master/DoctrineServiceProvider.php.
 */
final class DoctrineDbalServiceProvider implements ServiceProviderInterface
{
    /**
     * @param Container $container
     */
    public function register(Container $container)
    {
        $container['doctrine.dbal.connection_registry'] = $this->getDbConnectionRegistryDefintion($container);
        $container['doctrine.dbal.db'] = $this->getDbDefinition($container);
        $container['doctrine.dbal.db.cache_factory.apcu'] = $this->getDbApcuCacheFactoryDefinition($container);
        $container['doctrine.dbal.db.cache_factory.array'] = $this->getDbArrayCacheFactoryDefinition($container);
        $container['doctrine.dbal.db.config'] = $this->getDbConfigDefinition($container);
        $container['doctrine.dbal.db.default_options'] = $this->getDbDefaultOptions();
        $container['doctrine.dbal.db.event_manager'] = $this->getDbEventManagerDefinition($container);
        $container['doctrine.dbal.dbs'] = $this->getDbsDefinition($container);
        $container['doctrine.dbal.dbs.config'] = $this->getDbsConfigDefinition($container);
        $container['doctrine.dbal.dbs.event_manager'] = $this->getDbsEventManagerDefinition($container);
        $container['doctrine.dbal.dbs.options.initializer'] = $this->getDbsOptionsInitializerDefinition($container);
        $container['doctrine.dbal.types'] = [];
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbConnectionRegistryDefintion(Container $container): callable
    {
        return function ($container) {
            return new DoctrineDbalConnectionRegistry($container);
        };
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbDefinition(Container $container): callable
    {
        return function () use ($container) {
            $dbs = $container['doctrine.dbal.dbs'];

            return $dbs[$container['doctrine.dbal.dbs.default']];
        };
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbApcuCacheFactoryDefinition(Container $container): callable
    {
        return $container->protect(function (array $options) use ($container) {
            return new ApcuCache();
        });
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbArrayCacheFactoryDefinition(Container $container): callable
    {
        return $container->protect(function (array $options) use ($container) {
            return new ArrayCache();
        });
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbConfigDefinition(Container $container): callable
    {
        return function () use ($container) {
            $dbs = $container['doctrine.dbal.dbs.config'];

            return $dbs[$container['doctrine.dbal.dbs.default']];
        };
    }

    /**
     * @return array
     */
    private function getDbDefaultOptions(): array
    {
        return [
            'configuration' => [
                'auto_commit' => true,
                'cache.result' => ['type' => 'array'],
                'filter_schema_assets_expression' => null,
            ],
            'connection' => [
                'charset' => 'utf8mb4',
                'dbname' => null,
                'driver' => 'pdo_mysql',
                'host' => 'localhost',
                'password' => null,
                'path' => null,
                'port' => 3306,
                'user' => 'root',
            ],
        ];
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbEventManagerDefinition(Container $container): callable
    {
        return function () use ($container) {
            $dbs = $container['doctrine.dbal.dbs.event_manager'];

            return $dbs[$container['doctrine.dbal.dbs.default']];
        };
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbsDefinition(Container $container): callable
    {
        return function () use ($container) {
            $container['doctrine.dbal.dbs.options.initializer']();

            $dbs = new Container();
            foreach ($container['doctrine.dbal.dbs.options'] as $name => $options) {
                if ($container['doctrine.dbal.dbs.default'] === $name) {
                    $config = $container['doctrine.dbal.db.config'];
                    $manager = $container['doctrine.dbal.db.event_manager'];
                } else {
                    $config = $container['doctrine.dbal.dbs.config'][$name];
                    $manager = $container['doctrine.dbal.dbs.event_manager'][$name];
                }

                $dbs[$name] = function () use ($options, $config, $manager) {
                    return DriverManager::getConnection($options['connection'], $config, $manager);
                };
            }

            return $dbs;
        };
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbsConfigDefinition(Container $container): callable
    {
        return function () use ($container) {
            $container['doctrine.dbal.dbs.options.initializer']();

            $addLogger = $container['logger'] ?? false;

            $configs = new Container();
            foreach ($container['doctrine.dbal.dbs.options'] as $name => $options) {
                $configs[$name] = function () use ($addLogger, $container, $name, $options) {
                    $configOptions = $options['configuration'];

                    $config = new Configuration();

                    if ($addLogger) {
                        $config->setSQLLogger(new DoctrineDbalLogger($container['logger']));
                    }

                    $config->setResultCacheImpl($this->getCache($container, $configOptions['cache.result']));

                    $config->setFilterSchemaAssetsExpression($configOptions['filter_schema_assets_expression']);
                    $config->setAutoCommit($configOptions['auto_commit']);

                    return $config;
                };
            }

            return $configs;
        };
    }

    /**
     * @param Container    $container
     * @param string|array $cacheDefinition
     *
     * @return Cache
     */
    private function getCache(Container $container, $cacheDefinition): Cache
    {
        $cacheType = $cacheDefinition['type'];
        $cacheOptions = $cacheDefinition['options'] ?? [];

        $cacheFactory = $container[sprintf('doctrine.dbal.db.cache_factory.%s', $cacheType)];

        return $cacheFactory($cacheOptions);
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbsEventManagerDefinition(Container $container): callable
    {
        return function () use ($container) {
            $container['doctrine.dbal.dbs.options.initializer']();

            $managers = new Container();
            foreach ($container['doctrine.dbal.dbs.options'] as $name => $options) {
                $managers[$name] = function () {
                    return new EventManager();
                };
            }

            return $managers;
        };
    }

    /**
     * @param Container $container
     *
     * @return callable
     */
    private function getDbsOptionsInitializerDefinition(Container $container): callable
    {
        return $container->protect(function () use ($container) {
            static $initialized = false;

            if ($initialized) {
                return;
            }

            $initialized = true;

            foreach ((array) $container['doctrine.dbal.types'] as $typeName => $typeClass) {
                if (Type::hasType($typeName)) {
                    Type::overrideType($typeName, $typeClass);
                } else {
                    Type::addType($typeName, $typeClass);
                }
            }

            if (!isset($container['doctrine.dbal.dbs.options'])) {
                $container['doctrine.dbal.dbs.options'] = [
                    'default' => $container['doctrine.dbal.db.options'] ?? [],
                ];
            }

            $tmp = $container['doctrine.dbal.dbs.options'];
            foreach ($tmp as $name => &$options) {
                $options = array_replace_recursive($container['doctrine.dbal.db.default_options'], $options);

                if (!isset($container['doctrine.dbal.dbs.default'])) {
                    $container['doctrine.dbal.dbs.default'] = $name;
                }
            }

            $container['doctrine.dbal.dbs.options'] = $tmp;
        });
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Model;

use Abc\Bundle\JobBundle\Job\JobInterface as BaseJobInterface;
use Abc\Bundle\SchedulerBundle\Model\ScheduleInterface as BaseScheduleInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
abstract class JobManager implements JobManagerInterface
{
    /**
     * {@inheritDoc}
     */
    public function create($type = null, $parameters = null, BaseScheduleInterface $schedule = null)
    {
        $class = $this->getClass();

        /** @var JobInterface $job */
        $job   = new $class;

        $job->setType($type);
        $job->setParameters($parameters);

        if(!is_null($schedule))
        {
            $job->addSchedule($schedule);
        }

        return $job;
    }

    /**
     * {@inheritDoc}
     */
    public function findByTicket($ticket)
    {
        $jobs = $this->findBy(array('ticket' => $ticket));

        return count($jobs) > 0 ? $jobs[0] : null;
    }

    /**
     * {@inheritDoc}
     */
    public function isManagerOf(BaseJobInterface $job)
    {
        $class = $this->getClass();

        return ($job instanceof $class);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Job\Metadata\Driver;

use Abc\Bundle\JobBundle\Annotation\ParamType;
use Abc\Bundle\JobBundle\Annotation\ReturnType;
use Abc\Bundle\JobBundle\Job\Metadata\ClassMetadata;
use Doctrine\Common\Annotations\Reader;
use Metadata\Driver\DriverInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class AnnotationDriver implements DriverInterface
{
    /**
     * @var Reader
     */
    private $reader;

    /**
     * @param Reader $reader
     */
    public function __construct(Reader $reader)
    {
        $this->reader = $reader;
    }

    /**
     * @param \ReflectionClass $class
     *
     * @return \Metadata\ClassMetadata
     */
    public function loadMetadataForClass(\ReflectionClass $class)
    {
        $classMetadata                  = new ClassMetadata($name = $class->name);
        $classMetadata->fileResources[] = $class->getFilename();
        foreach ($class->getMethods() as $method) {
            /**
             * @var \ReflectionMethod $method
             */
            if ($method->class !== $name) {
                continue;
            }

            $methodAnnotations = $this->reader->getMethodAnnotations($method);
            foreach ($methodAnnotations as $annotation) {
                if ($annotation instanceof ParamType) {
                    if(!$classMetadata->hasMethod($method->name)) {
                        $this->addMethod($classMetadata, $method);
                    }
                    $classMetadata->setParameterType($method->getName(), $annotation->name, $annotation->type);
                    $classMetadata->setParameterOptions($method->getName(), $annotation->name, $annotation->options);
                }

                if ($annotation instanceof ReturnType) {
                    $classMetadata->setReturnType($method->getName(), $annotation->type);
                }
            }
        }

        return $classMetadata;
    }

    /**
     * @param ClassMetadata     $classMetadata
     * @param \ReflectionMethod $method
     */
    protected function addMethod(ClassMetadata $classMetadata, \ReflectionMethod $method)
    {
        $names = array();
        foreach ($method->getParameters() as $parameter) {
            $names[] = $parameter->getName();
        }

        $classMetadata->addMethod($method->getName(), $names);
    }
}


<?php

declare(strict_types=1);

namespace Chubbyphp\DoctrineDbServiceProvider\Command;

use Doctrine\Common\Persistence\ConnectionRegistry;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @see https://github.com/doctrine/DoctrineBundle/blob/master/Command/DropDatabaseDoctrineCommand.php
 */
final class DropDatabaseDoctrineCommand extends Command
{
    const RETURN_CODE_NOT_DROP = 1;

    const RETURN_CODE_NO_FORCE = 2;

    /**
     * @var ConnectionRegistry
     */
    private $connectionRegistry;

    /**
     * @param ConnectionRegistry $connectionRegistry
     */
    public function __construct(ConnectionRegistry $connectionRegistry)
    {
        parent::__construct();

        $this->connectionRegistry = $connectionRegistry;
    }

    protected function configure()
    {
        $this
            ->setName('dbal:database:drop')
            ->setDescription('Drops the configured database')
            ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
            ->addOption(
                'if-exists',
                null,
                InputOption::VALUE_NONE,
                'Don\'t trigger an error, when the database doesn\'t exist'
            )
            ->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action')
            ->setHelp(<<<EOT
The <info>%command.name%</info> command drops the default connections database:

    <info>php %command.full_name%</info>

The <info>--force</info> parameter has to be used to actually drop the database.

You can also optionally specify the name of a connection to drop the database for:

    <info>php %command.full_name% --connection=default</info>

<error>Be careful: All data in a given database will be lost when executing this command.</error>
EOT
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $connectionName = $this->getConnectionName($input);

        $connection = $this->connectionRegistry->getConnection($connectionName);

        $params = $this->getParams($connection);

        $dbName = $this->getDbName($params, $connectionName);

        if (!$input->getOption('force')) {
            $this->writeMissingForceOutput($output, $dbName, $connectionName);

            return self::RETURN_CODE_NO_FORCE;
        }

        $isPath = isset($params['path']);

        $ifExists = $input->getOption('if-exists');

        // Need to get rid of _every_ occurrence of dbname from connection configuration
        unset($params['dbname'], $params['path'], $params['url']);

        $connection->close();
        $connection = DriverManager::getConnection($params);
        $shouldDropDatabase = !$ifExists || in_array($dbName, $connection->getSchemaManager()->listDatabases());

        // Only quote if we don't have a path
        if (!$isPath) {
            $dbName = $connection->getDatabasePlatform()->quoteSingleIdentifier($dbName);
        }

        return $this->dropDatabase($output, $connectionName, $connection, $dbName, $shouldDropDatabase);
    }

    /**
     * @param InputInterface $input
     *
     * @return string
     */
    private function getConnectionName(InputInterface $input): string
    {
        $connectionName = $input->getOption('connection');

        if (null !== $connectionName) {
            return $connectionName;
        }

        return $this->connectionRegistry->getDefaultConnectionName();
    }

    /**
     * @param Connection $connection
     *
     * @return array
     */
    private function getParams(Connection $connection): array
    {
        $params = $connection->getParams();
        if (isset($params['master'])) {
            $params = $params['master'];
        }

        return $params;
    }

    /**
     * @param array  $params
     * @param string $connectionName
     *
     * @return string
     */
    private function getDbName(array $params, string $connectionName): string
    {
        if (isset($params['path'])) {
            return $params['path'];
        }

        if (isset($params['dbname'])) {
            return $params['dbname'];
        }

        throw new \InvalidArgumentException('Connection does not contain a \'path\' or \'dbname\' parameter.');
    }

    /**
     * @param OutputInterface $output
     * @param string          $dbName
     * @param string          $connectionName
     */
    private function writeMissingForceOutput(OutputInterface $output, string $dbName, string $connectionName)
    {
        $output->writeln(
            '<error>ATTENTION:</error> This operation should not be executed in a production environment.'
        );
        $output->writeln('');
        $output->writeln(
            sprintf(
                '<info>Would drop the database <comment>%s</comment> for connection'
                    .' named <comment>%s</comment>.</info>',
                $dbName,
                $connectionName
            )
        );
        $output->writeln('Please run the operation with --force to execute');
        $output->writeln('<error>All data will be lost!</error>');
    }

    /**
     * @param OutputInterface $output
     * @param string          $connectionName
     * @param Connection      $connection
     * @param string          $dbName
     * @param bool            $shouldNotCreateDatabase
     *
     * @return int
     */
    private function dropDatabase(
        OutputInterface $output,
        string $connectionName,
        Connection $connection,
        string $dbName,
        bool $shouldDropDatabase
    ): int {
        try {
            if ($shouldDropDatabase) {
                $connection->getSchemaManager()->dropDatabase($dbName);
                $output->writeln(
                    sprintf(
                        '<info>Dropped database <comment>%s</comment> for connection'
                            .' named <comment>%s</comment>.</info>',
                        $dbName,
                        $connectionName
                    )
                );
            } else {
                $output->writeln(
                    sprintf(
                        '<info>Database <comment>%s</comment> for connection named <comment>%s</comment>'
                            .' doesn\'t exist. Skipped.</info>',
                        $dbName,
                        $connectionName
                    )
                );
            }
        } catch (\Exception $e) {
            $output->writeln(
                sprintf(
                    '<error>Could not drop database <comment>%s</comment> for connection'
                        .' named <comment>%s</comment>.</error>',
                    $dbName,
                    $connectionName
                )
            );
            $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));

            return self::RETURN_CODE_NOT_DROP;
        }

        return 0;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Logger\Handler;

use Abc\Bundle\JobBundle\Job\JobInterface;
use Monolog\Handler\StreamHandler;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class StreamHandlerFactory extends BaseHandlerFactory
{
    /**
     * @var string
     */
    protected $path;

    /**
     * @var string
     */
    protected $extension;

    /**
     * @param string $path
     * @param string $extension
     */
    public function __construct($path, $extension = 'log')
    {
        $this->path      = $path;
        $this->extension = $extension;
    }

    /**
     * {@inheritdoc}
     */
    public function createHandler(JobInterface $job, $level, $bubble)
    {
        $handler = new StreamHandler($this->buildPath($job->getTicket()), $level, $bubble);

        return $this->initHandler($handler);
    }

    /**
     * @param string $filename
     * @return string The path of the logfile
     */
    private function buildPath($filename)
    {
        return $this->path . DIRECTORY_SEPARATOR . $filename . '.' . $this->extension;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Logger\Handler;

use Abc\Bundle\JobBundle\Job\JobInterface;
use Abc\Bundle\SchedulerBundle\Schedule\ProcessorInterface;
use Monolog\Formatter\FormatterInterface;
use Monolog\Handler\HandlerInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
abstract class BaseHandlerFactory implements HandlerFactoryInterface
{
    /**
     * @var FormatterInterface
     */
    protected $formatter;

    /**
     * @var array|ProcessorInterface[]
     */
    protected $processors = array();

    /**
     * {@inheritdoc}
     */
    public abstract function createHandler(JobInterface $job, $level, $bubble);

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter = null)
    {
        $this->formatter = $formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function setProcessors(array $processors)
    {
        $this->processors = $processors;
    }

    /**
     * Sets formatter and processors.
     *
     * @param HandlerInterface $handler
     * @return HandlerInterface
     */
    protected function initHandler(HandlerInterface $handler)
    {
        if ($this->formatter != null) {
            $handler->setFormatter($this->formatter);
        }

        foreach ($this->processors as $processor) {
            $handler->pushProcessor($processor);
        }

        return $handler;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('abc_job');

        $supportedDrivers = ['orm', 'custom'];
        $supportedAdapters = ['bernard', 'sonata', 'custom'];
        $supportedLogStorages = ['file', 'orm', 'custom'];
        $supportedLogLevels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert'];

        $rootNode
            ->children()
                ->scalarNode('db_driver')
                    ->defaultValue('orm')
                    ->validate()
                        ->ifNotInArray($supportedDrivers)
                        ->thenInvalid('The driver %s is not supported. Please choose one of ' . json_encode($supportedDrivers))
                    ->end()
                ->end()
                ->scalarNode('adapter')
                    ->validate()
                        ->ifNotInArray($supportedAdapters)
                        ->thenInvalid('The adapter %s is not supported. Please choose one of ' . json_encode($supportedAdapters))
                    ->end()
                    ->isRequired()
                ->end()
                ->arrayNode('manager')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->scalarNode('validate')->defaultTrue()->end()
                    ->end()
                ->end()
                ->scalarNode('default_queue')->defaultValue('default')->end()
                ->arrayNode('queues')
                    ->useAttributeAsKey('name')
                    ->prototype('array')
                        ->prototype('scalar')->end()
                    ->end()
                ->end()
                ->booleanNode('register_default_jobs')
                    ->defaultFalse()
                ->end()
                ->scalarNode('connection')
                    ->defaultValue('default')
                ->end()
                ->scalarNode('model_manager_name')
                    ->defaultNull()
                ->end()
                ->arrayNode('controller')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->scalarNode('refresh_interval')->defaultValue(1)->end()
                    ->end()
                ->end()
                ->arrayNode('rest')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->booleanNode('enable')->defaultTrue()->end()
                        ->scalarNode('validate')->defaultFalse()->end()
                    ->end()
                ->end()
                ->arrayNode('logging')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->arrayNode('storage_handler')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->scalarNode('type')
                                ->defaultValue('file')
                                ->validate()
                                    ->ifNotInArray($supportedLogStorages)
                                    ->thenInvalid('The storage type %s is not supported. Please choose one of ' . json_encode($supportedLogStorages))
                                ->end()
                                ->cannotBeEmpty()
                                ->end()
                                ->scalarNode('path')->defaultValue('%kernel.logs_dir%')->end()
                                ->scalarNode('level')
                                    ->defaultValue('info')
                                    ->validate()
                                        ->ifNotInArray($supportedLogLevels)
                                        ->thenInvalid('The level %s is not supported. Please choose one of ' . json_encode($supportedLogLevels))
                                    ->end()
                                ->end()
                                ->booleanNode('bubble')->defaultValue(true)->end()
                                ->arrayNode('processor')
                                    ->canBeUnset()
                                    ->useAttributeAsKey('name')
                                    ->prototype('scalar')->end()
                                ->end()
                            ->end()
                        ->end()

                        ->arrayNode('stream_handler')
                            ->canBeUnset()
                            ->children()
                                ->scalarNode('path')->defaultValue('%kernel.logs_dir%')->end()
                                ->scalarNode('level')
                                    ->defaultValue('info')
                                    ->validate()
                                        ->ifNotInArray($supportedLogLevels)
                                        ->thenInvalid('The level %s is not supported. Please choose one of ' . json_encode($supportedLogLevels))
                                    ->end()
                                ->end()
                                ->booleanNode('bubble')->defaultTrue()->end()
                                ->scalarNode('formatter')->defaultNull()->end()
                                ->arrayNode('processor')
                                    ->canBeUnset()
                                    ->useAttributeAsKey('name')
                                    ->prototype('scalar')->end()
                                ->end()
                            ->end()
                        ->end()
                        ->arrayNode('handler')
                            ->canBeUnset()
                            ->useAttributeAsKey('name')
                            ->prototype('scalar')->end()
                        ->end()
                        ->arrayNode('level')
                            ->useAttributeAsKey('name')
                            ->prototype('scalar')->end()
                        ->end()
                    ->end()
                ->end()
            ->end();

        $this->addServiceSection($rootNode);

        return $treeBuilder;
    }

    /**
     * @param ArrayNodeDefinition $node
     */
    private function addServiceSection(ArrayNodeDefinition $node)
    {
        $node
            ->addDefaultsIfNotSet()
                ->children()
                    ->arrayNode('service')
                        ->addDefaultsIfNotSet()
                        ->children()
                            ->scalarNode('manager')->defaultValue('abc.job.manager.default')->end()
                            ->scalarNode('job_manager')->defaultValue('abc.job.job_manager.default')->end()
                            ->scalarNode('schedule_manager')->defaultValue('abc.job.schedule_manager.default')->end()
                            ->scalarNode('schedule_iterator')->defaultValue('abc.job.schedule_iterator.default')->end()
                            ->scalarNode('schedule_manager_iterator')->defaultValue('abc.job.schedule_manager_iterator.default')->end()
                            ->scalarNode('controller_factory')->defaultValue('abc.job.controller_factory.default')->end()
                            ->scalarNode('controller')->defaultValue('abc.process_control.controller')->end()
                            ->scalarNode('locker')->defaultValue('abc.job.locker.default')->end()
                            ->scalarNode('queue_config')->defaultValue('abc.job.queue_config.default')->end()
                            ->scalarNode('serializer')->defaultValue('abc.job.serializer.default')->end()
                            ->scalarNode('validator')->defaultValue('validator')->end()
                        ->end()
                    ->end()
                ->end()
            ->end();
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Serializer\EventDispatcher;

use Abc\Bundle\JobBundle\Model\Job;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class JobDeserializationSubscriber implements EventSubscriberInterface
{
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            ['event' => 'serializer.pre_deserialize', 'method' => 'onPreDeserialize']
        ];
    }

    /**
     * Appends an array element to the parameters of a job that provides information about the job type.
     *
     * @param PreDeserializeEvent $event
     */
    public function onPreDeserialize(PreDeserializeEvent $event)
    {
        $type = $event->getType();
        if (isset($type['name']) && ($type['name'] == Job::class || is_subclass_of($type['name'], Job::class))) {
            $data = $event->getData();
            if (isset($data['type']) && isset($data['parameters']) && is_array($data['parameters']) && count($data['parameters']) > 0) {
                array_push($data['parameters'], ['abc.job.type' => $data['type']]);
                $event->setData($data);
            }
        }
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Doctrine;

use Abc\Bundle\JobBundle\Model\ScheduleInterface;
use Abc\Bundle\JobBundle\Model\ScheduleManagerInterface;
use Abc\Bundle\SchedulerBundle\Doctrine\ScheduleManager as BaseScheduleManager;

/**
 * Doctrine EntityManager for entities of type Abc\Bundle\JobBundle\Model\ScheduleInterface
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ScheduleManager extends BaseScheduleManager implements ScheduleManagerInterface
{
    /**
     * {@inheritDoc}
     */
    public function create($type = null, $expression = null, $active = true)
    {
        $class = $this->getClass();

        /** @var ScheduleInterface $schedule */
        $schedule = new $class;
        $schedule->setType($type);
        $schedule->setExpression($expression);
        $schedule->setIsActive($active == null ? true : $active);

        return $schedule;
    }

    /**
     * {@inheritDoc}
     */
    public function findSchedules($limit = null, $offset = null)
    {
        return $this->repository->findBy(array('isActive' => true), array(), $limit, $offset);
    }

    /**
     * {@inheritDoc}
     */
    public function delete(ScheduleInterface $schedule)
    {
        $this->objectManager->remove($schedule);
        $this->objectManager->flush();
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Model;

use Abc\Bundle\JobBundle\Job\JobInterface as BaseJobInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
abstract class LogManager implements LogManagerInterface
{
    /**
     * {@inheritDoc}
     */
    public function create()
    {
        $class = $this->getClass();

        /**
         * @var LogInterface $log
         */
        return new $class;
    }

    /**
     * {@inheritdoc}
     */
    public function findByJob(BaseJobInterface $job)
    {
        $records = array();
        foreach ($this->findBy(['jobTicket' => $job->getTicket()], ['datetime' => 'ASC']) as $log) {
            /**
             * @var LogInterface $log
             */
            $records[] = $log->toRecord();
        }

        return $records;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteByJob(BaseJobInterface $job)
    {
        return $this->deleteLogs($this->findBy(['jobTicket' => $job->getTicket()]));
    }

    /**
     * @param LogInterface[] $logs
     * @return int the number of delete entries
     */
    protected function deleteLogs($logs)
    {
        $i = 0;
        foreach ($logs as $log) {
            $this->delete($log);
            $i++;
        }

        return $i;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Validator\Constraints;

use Abc\Bundle\JobBundle\Job\JobTypeRegistry;
use Abc\Bundle\JobBundle\Validator\Job\ConstraintProviderInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ParametersValidator extends ConstraintValidator
{
    /**
     * @var JobTypeRegistry
     */
    private $registry;

    /**
     * @var ConstraintProviderInterface[]
     */
    private $providers = array();

    /**
     * @var array
     */
    private $constraints;

    /**
     * @param JobTypeRegistry $registry
     */
    public function __construct(JobTypeRegistry $registry)
    {
        $this->registry = $registry;
    }

    /**
     * {@inheritdoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (!is_array($this->constraints)) {
            $this->initializeConstraints();
        }

        if (!$constraint->type) {
            throw new ConstraintDefinitionException('"type" must be specified on constraint Parameters');
        }

        if (!isset($this->constraints[$constraint->type])) {
            return;
        }

        if (null !== $value && !is_array($value)) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();

            return;
        }

        $contextualValidator = $this->context->getValidator()->inContext($this->context);
        if (isset($this->constraints[$constraint->type])) {
            foreach ($this->constraints[$constraint->type] as $index => $constraint) {
                $contextualValidator
                    ->atPath('[' . $index . ']')
                    ->validate(isset($value[$index]) ? $value[$index] : null, $constraint);
            }
        }
    }

    /**
     * @param ConstraintProviderInterface $provider
     * @return void
     */
    public function register(ConstraintProviderInterface $provider)
    {
        $this->providers[] = $provider;
    }

    private function initializeConstraints()
    {
        $this->constraints = array();
        $priority          = [];
        foreach ($this->providers as $provider) {
            foreach ($this->registry->getTypeChoices() as $type) {
                $constraints = $provider->getConstraints($type);
                if (is_array($constraints) && count($constraints) > 0) {
                    if (!isset($this->constraints[$type]) || $provider->getPriority() > $priority[$type]) {
                        $this->constraints[$type] = $constraints;
                        $priority[$type]          = $provider->getPriority();
                    }
                }
            }
        }
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Job\Queue;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class QueueConfig implements QueueConfigInterface
{
    /**
     * @var array
     */
    private $map;

    /**
     * @var string
     */
    private $defaultQueue;

    /**
     * @param array  $queueMapping
     * @param string $defaultQueue
     */
    public function __construct(array $queueMapping = [], $defaultQueue = 'default')
    {
        $this->defaultQueue = $defaultQueue;
        foreach ($queueMapping as $queueName => $jobTypes) {
            foreach ($jobTypes as $jobType) {
                if(!isset($this->map[$jobType])) {
                    $this->map[$jobType] = $queueName;
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultQueue()
    {
        return $this->defaultQueue;
    }

    /**
     * {@inheritdoc}
     */
    public function getQueue($type)
    {
        return isset($this->map[$type]) ? $this->map[$type] : $this->defaultQueue;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Adapter\Sonata;

use Abc\Bundle\JobBundle\Job\Queue\ConsumerInterface;
use Abc\ProcessControl\ControllerInterface;
use Sonata\NotificationBundle\Backend\BackendInterface;
use Sonata\NotificationBundle\Event\IterateEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ConsumerAdapter implements ConsumerInterface
{
    /**
     * @var BackendProvider
     */
    private $backendProvider;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * @var EventDispatcherInterface
     */
    private $notificationDispatcher;

    /**
     * @var ControllerInterface
     */
    private $controller;

    /**
     * @var array
     */
    protected $options = [
        'max-runtime'     => PHP_INT_MAX,
        'max-messages'    => null,
        'stop-when-empty' => false,
        'stop-on-error'   => false,
    ];

    /**
     * @param BackendProvider          $backendProvider
     * @param EventDispatcherInterface $evendDispatcher
     * @param EventDispatcherInterface $notificationDispatcher
     * @param ControllerInterface      $controller
     */
    public function __construct(
        BackendProvider $backendProvider,
        EventDispatcherInterface $evendDispatcher,
        EventDispatcherInterface $notificationDispatcher,
        ControllerInterface $controller
    )
    {
        $this->backendProvider        = $backendProvider;
        $this->eventDispatcher        = $evendDispatcher;
        $this->notificationDispatcher = $notificationDispatcher;
        $this->controller             = $controller;
    }

    /**
     * Starts an infinite loop calling Consumer::tick();
     *
     * @param string $queue
     * @param array  $options
     */
    public function consume($queue, array $options = [])
    {
        $backend = $this->backendProvider->getBackend($queue);

        $backend->initialize();

        declare (ticks = 1);

        while ($this->tick($backend, $options)) {
            // NO op
        }
    }

    protected function tick(BackendInterface $backend, array $options = [])
    {
        $this->configure($options);

        $iterator = $backend->getIterator();

        foreach ($iterator as $message) {

            if ($this->controller->doPause()) {
                return true;
            }

            if ($this->controller->doStop()) {
                return false;
            }

            if (microtime(true) > $this->options['max-runtime']) {
                return false;
            }

            $backend->handle($message, $this->notificationDispatcher);

            $this->eventDispatcher->dispatch(IterateEvent::EVENT_NAME, new IterateEvent($iterator, $backend, $message));

            if (null !== $this->options['max-messages'] && !(boolean)--$this->options['max-messages']) {
                return false;
            }
        }

        return !$this->options['stop-when-empty'];
    }

    /**
     * @param array $options
     * @return void
     */
    protected function configure(array $options)
    {
        $this->options = array_filter($options) + $this->options;
        $this->options['max-runtime'] += microtime(true);
        $this->configured = true;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Entity;

use Abc\Bundle\JobBundle\Doctrine\JobManager as BaseJobManager;
use Abc\Bundle\JobBundle\Doctrine\ScheduleManager;
use Abc\Bundle\JobBundle\Serializer\Job\SerializationHelper;
use Doctrine\ORM\EntityManager;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class JobManager extends BaseJobManager
{
    /**
     * @var EntityManager
     */
    protected $em;

    /**
     * @param EntityManager       $em
     * @param string              $class
     * @param ScheduleManager     $scheduleManager
     * @param SerializationHelper $serializationHelper
     */
    public function __construct(EntityManager $em, $class, ScheduleManager $scheduleManager, SerializationHelper $serializationHelper)
    {
        parent::__construct($em, $class, $scheduleManager, $serializationHelper);

        $this->em = $em;
    }

    /**
     * @inheritdoc
     */
    public function findByCount(array $criteria)
    {
        $queryBuilder = $this->createQueryBuilder();

        $queryBuilder->select(sprintf('COUNT(%s)', $this->getAlias()));

        $queryBuilder = $this->buildMatchingQueryForCriteria($queryBuilder, $criteria);

        return $queryBuilder->getQuery()->getSingleScalarResult();
    }

    /**
     * @param \Doctrine\ORM\QueryBuilder $queryBuilder
     * @param array                      $criteria
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function buildMatchingQueryForCriteria($queryBuilder, array $criteria)
    {
        foreach ($criteria as $key => $value) {

            $operator = ' = :%s';

            if (is_array($value)) {

                if (count($value) == 1 && array_keys($value)[0] === '$match') {

                    $firstValue = reset($value);

                    //Only like is supported here at the moment
                    $operator = ' LIKE :%s';
                    $value    = '%' . $firstValue . '%';

                } else {
                    $operator = ' IN (:%s)';
                }
            }

            $queryBuilder->andWhere($this->getAlias() . '.' . $key . sprintf($operator, $key))
                ->setParameter($key, $value);
        }

        return $queryBuilder;
    }

    /**
     * @return string
     */
    protected function getAlias()
    {
        return 'job';
    }

    /**
     * Creates a new QueryBuilder instance that is prepopulated for this entity name
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function createQueryBuilder()
    {
        return $this->em->getRepository($this->getClass())->createQueryBuilder($this->getAlias());
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Doctrine;

use Abc\Bundle\JobBundle\Model\JobInterface;
use Abc\Bundle\JobBundle\Model\JobManager as BaseJobManager;
use Abc\Bundle\JobBundle\Serializer\Job\SerializationHelper;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\ObjectRepository;

/**
 * Entity manager for entities of type JobInterface
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
abstract class JobManager extends BaseJobManager
{
    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * @var string
     */
    protected $class;

    /**
     * @var ObjectRepository
     */
    protected $repository;

    /**
     * @var ScheduleManager
     */
    protected $scheduleManager;

    /**
     * Constructor.
     *
     * @param ObjectManager       $om
     * @param string              $class
     * @param ScheduleManager     $scheduleManager
     * @param SerializationHelper $serializationHelper
     */
    public function __construct(ObjectManager $om, $class, ScheduleManager $scheduleManager, SerializationHelper $serializationHelper)
    {
        $this->objectManager   = $om;
        $this->repository      = $om->getRepository($class);
        $this->scheduleManager = $scheduleManager;

        $metadata    = $om->getClassMetadata($class);
        $this->class = $metadata->getName();

        Job::setSerializationHelper($serializationHelper);
    }

    /**
     * {@inheritDoc}
     */
    public function getClass()
    {
        return $this->class;
    }

    /**
     * @param JobInterface $job
     * @param bool         $andFlush Whether to flush the changes (default true)
     * @return void
     */
    public function save(JobInterface $job, $andFlush = true)
    {
        $this->objectManager->persist($job);

        if($andFlush)
        {
            $this->objectManager->flush();
        }
    }

    /**
     * {@inheritDoc}
     */
    public function delete(JobInterface $job)
    {
        $this->objectManager->remove($job);
        $this->objectManager->flush();
    }

    /**
     * {@inheritDoc}
     */
    public function refresh(JobInterface $job)
    {
        $this->objectManager->refresh($job);
    }

    /**
     * {@inheritDoc}
     */
    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
    {
        return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
    }

    /**
     * {@inheritDoc}
     */
    public function findAll()
    {
        return $this->repository->findAll();
    }
}


<?php

declare(strict_types=1);

namespace Chubbyphp\DoctrineDbServiceProvider\Driver;

use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;

class ClassMapDriver implements MappingDriver
{
    /**
     * @var array|string[]
     */
    private $classMap;

    /**
     * @param array|string[] $classMap
     */
    public function __construct(array $classMap)
    {
        $this->classMap = $classMap;
    }

    /**
     * @param string                 $className
     * @param ClassMetadataInterface $metadata
     *
     * @throws MappingException
     */
    public function loadMetadataForClass($className, ClassMetadataInterface $metadata)
    {
        if (false === $metadata instanceof ClassMetadata) {
            throw new MappingException(
                sprintf('Metadata is of class "%s" instead of "%s"', get_class($metadata), ClassMetadata::class)
            );
        }

        if (false === isset($this->classMap[$className])) {
            throw new MappingException(
                sprintf('No configured mapping for document "%s"', $className)
            );
        }

        $mappingClassName = $this->classMap[$className];

        if (false === ($mapping = new $mappingClassName()) instanceof ClassMapMappingInterface) {
            throw new MappingException(
                sprintf('Class "%s" does not implement the "%s"', $mappingClassName, ClassMapMappingInterface::class)
            );
        }

        $mapping->configureMapping($metadata);
    }

    /**
     * @return array
     */
    public function getAllClassNames(): array
    {
        return array_keys($this->classMap);
    }

    /**
     * @param string $className
     *
     * @return bool
     */
    public function isTransient($className): bool
    {
        if (isset($this->classMap[$className])) {
            return false;
        }

        return true;
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Logger\Handler;

use Abc\Bundle\JobBundle\Job\JobAwareInterface;
use Abc\Bundle\JobBundle\Job\JobInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class JobAwareOrmHandler extends OrmHandler implements JobAwareInterface
{
    /**
     * @var JobInterface
     */
    protected $job;

    /**
     * {@inheritdoc}
     */
    public function setJob(JobInterface $job)
    {
        $this->job = $job;
    }

    /**
     * Sets the job ticket in $record['extra']['job_ticket']
     *
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $log = $this->manager->create();

        $record['extra']['job_ticket'] = $this->job->getTicket();

        $this->populateLog($log, $record);

        $this->manager->save($log);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Command;

use Abc\Bundle\JobBundle\Job\Queue\ConsumerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class ConsumerCommand extends Command
{
    /**
     * @var ConsumerInterface
     */
    private $consumer;

    /**
     * @param ConsumerInterface $consumer
     */
    public function __construct(ConsumerInterface $consumer)
    {
        parent::__construct();
        $this->consumer = $consumer;
    }

    /**
     * {@inheritDoc}
     */
    public function configure()
    {
        $this->setName('abc:job:consume');
        $this->setDescription('Consume jobs from a queue');

        $this
            ->addArgument('queue', InputArgument::REQUIRED, 'Name of queue that will be consumed.')
            ->addOption('max-runtime', null, InputOption::VALUE_OPTIONAL, 'Maximum time in seconds the consumer will run.', null)
            ->addOption('max-messages', null, InputOption::VALUE_OPTIONAL, 'Maximum number of messages that should be consumed.', null)
            ->addOption('stop-when-empty', null, InputOption::VALUE_NONE, 'Stop consumer when queue is empty.', null);
    }

    /**
     * {@inheritDoc}
     */
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $this->consumer->consume($input->getArgument('queue'), [
            'max-runtime'     => $input->getOption('max-runtime'),
            'max-messages'    => $input->getOption('max-messages'),
            'stop-when-empty' => $input->getOption('stop-when-empty')
        ]);
    }
}


<?php
/*
* This file is part of the job-bundle package.
*
* (c) Hannes Schulz <hannes.schulz@aboutcoders.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Abc\Bundle\JobBundle\Job;

use Abc\Bundle\JobBundle\Logger\LoggerFactoryInterface;
use Abc\Bundle\JobBundle\Model\JobInterface as EntityJobInterface;
use Psr\Log\LoggerInterface;

/**
 * Helper class to manage jobs.
 *
 * @author Hannes Schulz <hannes.schulz@aboutcoders.com>
 */
class JobHelper
{
    /**
     * @var LoggerFactoryInterface
     */
    protected $loggerFactory;

    /**
     * @param LoggerFactoryInterface $loggerFactory
     */
    function __construct(LoggerFactoryInterface $loggerFactory)
    {
        $this->loggerFactory = $loggerFactory;
    }

    /**
     * Updates a job.
     *
     * If the given status is ERROR or CANCELLED the child jobs of the given job will be terminated with status CANCELLED.
     *
     * @param EntityJobInterface $job
     * @param Status             $status
     * @param int                $processingTime
     * @param mixed|null         $response
     */
    public function updateJob(EntityJobInterface $job, Status $status, $processingTime = 0, $response = null)
    {
        $job->setStatus($status);
        $job->setProcessingTime($job->getProcessingTime() + ($processingTime === null ? 0 : $processingTime));
        $job->setResponse($response);

        if (Status::isTerminated($status)) {
            $job->setTerminatedAt(new \DateTime());
        }

        if ($job->hasSchedules() && Status::isTerminated($status)) {
            foreach ($job->getSchedules() as $schedule) {
                if (method_exists($schedule, 'setIsActive')) {
                    $schedule->setIsActive(false);
                }
            }
        }
    }

    /**
     * @param EntityJobInterface $job
     * @return LoggerInterface
     */
    public function getJobLogger(EntityJobInterface $job)
    {
        return $this->loggerFactory->create($job);
    }

    /**
     * Copies properties of a job to another job
     *
     * @param JobInterface                             $from The job where properties are copied from
     * @param \Abc\Bundle\JobBundle\Model\JobInterface $to   The job where where properties are copied to
     * @return \Abc\Bundle\JobBundle\Model\JobInterface The copied job
     */
    public function copyJob(JobInterface $from, \Abc\Bundle\JobBundle\Model\JobInterface $to)
    {
        $to->setType($from->getType());
        $to->setResponse($from->getResponse());
        $to->setParameters($from->getParameters());

        if (null != $from->getStatus()) {
            $to->setStatus($from->getStatus());
        }

        foreach ($from->getSchedules() as $schedule) {
            $to->addSchedule($schedule);
        }

        return $to;
    }
}