Laravel ( Illuminate ) Components - Schedule

Este no es un componente pero si la joya de la corona del Console de Laravel por lo que merece un tratamiento especial.

En este capitulo veremos como usar el Schedule de Laravel fuera del framework para que podamos usarlo como mejor nos parezca.

Lo primero que vamos hacer es crear un archivo de ejecución con nombre de artisan (ya que se no hace familiar), este sera el que ejecutara nuestro cron tal como esta en la documentación.

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

Antes de comenzar os dejo el repo de github , por que a veces es mas sencillo ver todo desde alli! :D

Veamos nuestro artisan

#!/usr/bin/env php
<?php

use Schedule\Kernel;
use Schedule\Application;
use Illuminate\Events\Dispatcher;

require __DIR__.'/vendor/autoload.php';

$app = new Application;

$app['config'] = [
    'cache.default' => 'file',
    'cache.stores.file' => [
        'driver' => 'file',
        'path' => __DIR__ . '/cache'
    ]
];

$app['files'] = new Filesystem;

$kernel = new Kernel($app, new Dispatcher($app));

$kernel->handle(
    new Symfony\Component\Console\Input\ArgvInput,
    new Symfony\Component\Console\Output\ConsoleOutput
);

Ahora veamos de que va esto, tenemos un Application y un Kernel que se encargara de manejar los diferentes comandos que le pasamos a nuestro artisan.

El Application de Laravel solo es un Container vitaminizado, así que nosotros crearemos nuestro Application solo con las cosas que necesita nuestro Schedule.

veamos las dependencias para nuestro proyecto antes de continuar

{
    "require": {
        "illuminate/console": "^5.8",
        "illuminate/events": "^5.8",
        "illuminate/cache": "^5.8",
        "illuminate/filesystem": "^5.8",
        "dragonmantank/cron-expression": "^2.3"
    },
    "autoload": {
        "psr-4": {
            "Schedule\\": "src/"
        },
        "files": [
            "helpers.php"
        ]
    }
}

Ahora si el Application

<?php

namespace Schedule;

use Illuminate\Cache\CacheManager;
use Illuminate\Console\Scheduling\CacheEventMutex;
use Illuminate\Console\Scheduling\EventMutex;
use Illuminate\Container\Container;
use Illuminate\Contracts\Cache\Factory;

class Application extends Container
{
    public function __construct()
    {
        $this->registerBaseBindings();
        $this->registerScheduleBindings();
    }

    protected function registerBaseBindings()
    {
        static::setInstance($this);
    }

    protected function registerScheduleBindings()
    {
        $this->bind(
            Factory::class,
            function ($app) {
                return new CacheManager($app);
            }
        );

        $this->bind(EventMutex::class, CacheEventMutex::class);
        $this->bind(SchedulingMutex::class, CacheSchedulingMutex::class);
    }

    public function environment()
    {
        return 'prod';
    }

    public function isDownForMaintenance()
    {
        return false;
    }
}

Es un Application bastante sencillo como podemos ver, tenemos un registerBaseBindings que se encarga de setear nuestra instancia para que librerías que usan el container puedan acceder a este de manera global, luego tenemos un registerScheduleBindings en donde registramos un CacheManager y los Mutex estos ultimos se encarga de asignar un ID unico a los los eventos y al schedule para que no se solapen y se guardan en cache para que hacer el proceso mas eficiente.

Luego tenemos un environment y isDownForMaintenance , el primero donde retornamos nuestro entorno, muy util si tenemos tareas en nuestro Schedule con diferentes entornos y el segundo es para apagar el Schedule ambas que pueden llevar a un archivo de configuración. (os dejo los cambio menores).

Ahora desmosle una mirada al nuestro Kernel

<?php

namespace Schedule;

use Illuminate\Console\Application as Artisan;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\ScheduleRunCommand;
use Illuminate\Events\Dispatcher;

class Kernel
{
    protected $app;
    protected $artisan;
    protected $events;

    protected $commands = [
        #Register command here!!
    ];

    public function __construct(Application $app, Dispatcher $events)
    {
        $this->app = $app;
        $this->events = $events;
    }

    public function handle($input, $output = null)
    {
        $this->bootstrap();
        return $this->getArtisan()->run($input, $output);
    }

    protected function schedule(Schedule $schedule)
    {
         #Schedule comands here!!
    }

    protected function bootstrap()
    {
        $schedule = new Schedule();

        $this->getArtisan()->add(
            new ScheduleRunCommand($schedule)
        );

        $this->schedule($schedule);
    }

    protected function getArtisan()
    {
        if (is_null($this->artisan)) {
            return $this->artisan = (new Artisan($this->app, $this->events, '5.8'))
                ->resolveCommands($this->commands);
        }
        return $this->artisan;
    }
}

Tenemos como de costumbre un Array donde registrar nuestros commands y un método schedule donde agregaremos nuestras tareas. Toda la lógica de ejecución reside en el método handle que se encargara de resolver los comandos que le pasamos a artisan.

Y por ultimo tenemos un helper que usa Schedule para saber donde esta parado :D

<?php

if (! function_exists('base_path')) {
    /**
     * Get the path to the base of the install.
     *
     * @param  string  $path
     * @return string
     */
    function base_path($path = '')
    {
        return __DIR__;
    }
}

Nunca fue tan fácil usar Schedule!

Espero os sea de ayuda! Alguna duda o sugerencia la podéis dejar como comentario o hacerme una mencion en twitter @millancore