Full Code of omnilight/yii2-scheduling for AI

master f0c63f294312 cached
18 files
35.0 KB
9.2k tokens
77 symbols
1 requests
Download .txt
Repository: omnilight/yii2-scheduling
Branch: master
Commit: f0c63f294312
Files: 18
Total size: 35.0 KB

Directory structure:
gitextract_zj2jjtom/

├── .dockerignore
├── .github/
│   └── workflows/
│       └── php.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── docker-compose.yml
├── phpunit.xml.dist
├── src/
│   ├── Bootstrap.php
│   ├── CallbackEvent.php
│   ├── Event.php
│   ├── Schedule.php
│   └── ScheduleController.php
└── tests/
    ├── EventTest.php
    └── bootstrap.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
vendor
.idea
.dockerignore
.git*
.travis.yml
composer.lock
docker-compose.yml
Dockerfile


================================================
FILE: .github/workflows/php.yml
================================================
name: PHP Composer

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
      
    - uses: shivammathur/setup-php@v2
      with:
        php-version: '5.4'
        extensions: mbstring

    - name: Validate composer.json and composer.lock
      run: composer validate

    - name: Install dependencies
      run: composer install --prefer-dist --no-progress --no-suggest

    - name: Run test suite
      run: composer run-script test


================================================
FILE: .gitignore
================================================
.idea
vendor
composer.lock


================================================
FILE: .travis.yml
================================================
language: php

php:
  - 5.4

env:
  global:
    - DEFAULT_COMPOSER_FLAGS="--prefer-dist --no-interaction --no-progress --optimize-autoloader"

cache:
  directories:
    - vendor
    - $HOME/.composer/cache

before_install:
  - phpenv config-rm xdebug.ini || true
  - travis_retry composer self-update
  - composer install $DEFAULT_COMPOSER_FLAGS

script: vendor/bin/phpunit --verbose


================================================
FILE: Dockerfile
================================================
FROM composer

FROM php:5.4-cli

RUN apt-get update && \
    apt-get install -y \
        git \
        unzip

RUN docker-php-ext-install mbstring

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN composer global require hirak/prestissimo

WORKDIR /app

COPY composer.json .

RUN composer install --prefer-dist --no-interaction --no-ansi

COPY . .


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2015 Pavel Agalecky <pavel.agalecky@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
docker_build = (docker-compose build)

build:
	$(call docker_build)

test:
	$(call docker_build) && docker-compose run app composer run-script test


================================================
FILE: README.md
================================================
Schedule extension for Yii2
===========================

This extension is the port of Laravel's Schedule component (https://laravel.com/docs/master/scheduling#scheduling-artisan-commands)

Installation
------------

The preferred way to install this extension is through [composer](http://getcomposer.org/download/).

Either run

```
php composer.phar require omnilight/yii2-scheduling "*"
```

or add

```json
"omnilight/yii2-scheduling": "*"
```

to the `require` section of your composer.json.

Description
-----------

This project is inspired by the Laravel's Schedule component and tries to bring it's simplicity to the Yii framework.
Quote from Laravel's documentation:

```
In the past, developers have generated a Cron entry for each console command they wished to schedule.
However, this is a headache. Your console schedule is no longer in source control,
and you must SSH into your server to add the Cron entries. Let's make our lives easier.
```

After installation all you have to do is to put single line into crontab:

```
* * * * * php /path/to/yii yii schedule/run --scheduleFile=@path/to/schedule.php 1>> /dev/null 2>&1
```

You can put your schedule into the `schedule.php` file, or add it withing bootstrapping of your extension or
application

Schedule examples
-----------------

This extension is support all features of Laravel's Schedule, except environments and maintance mode.

**Scheduling Closures**

```php
$schedule->call(function()
{
    // Do some task...

})->hourly();
```

**Scheduling Terminal Commands**

```php
$schedule->exec('composer self-update')->daily();
```

**Running command of your application**

```php
$schedule->command('migrate')->cron('* * * * *');
```

**Frequent Jobs**

```php
$schedule->command('foo')->everyFiveMinutes();

$schedule->command('foo')->everyTenMinutes();

$schedule->command('foo')->everyThirtyMinutes();
```

**Daily Jobs**

```php
$schedule->command('foo')->daily();
```

**Daily Jobs At A Specific Time (24 Hour Time)**

```php
$schedule->command('foo')->dailyAt('15:00');
```

**Twice Daily Jobs**

```php
$schedule->command('foo')->twiceDaily();
```

**Job That Runs Every Weekday**

```php
$schedule->command('foo')->weekdays();
```

**Weekly Jobs**

```php
$schedule->command('foo')->weekly();

// Schedule weekly job for specific day (0-6) and time...
$schedule->command('foo')->weeklyOn(1, '8:00');
```

**Monthly Jobs**

```php
$schedule->command('foo')->monthly();
```

**Job That Runs On Specific Days**

```php
$schedule->command('foo')->mondays();
$schedule->command('foo')->tuesdays();
$schedule->command('foo')->wednesdays();
$schedule->command('foo')->thursdays();
$schedule->command('foo')->fridays();
$schedule->command('foo')->saturdays();
$schedule->command('foo')->sundays();
```

**Only Allow Job To Run When Callback Is True**

```php
$schedule->command('foo')->monthly()->when(function()
{
    return true;
});
```

**E-mail The Output Of A Scheduled Job**

```php
$schedule->command('foo')->sendOutputTo($filePath)->emailOutputTo('foo@example.com');
```

**Preventing Task Overlaps**

```php
$schedule->command('foo')->withoutOverlapping();
```
Used by default yii\mutex\FileMutex or 'mutex' application component (http://www.yiiframework.com/doc-2.0/yii-mutex-mutex.html)

**Running Tasks On One Server**

>To utilize this feature, you must config mutex in the application component, except the FileMutex:  `yii\mutex\MysqlMutex`,`yii\mutex\PgsqlMutex`,`yii\mutex\OracleMutex` or `yii\redis\Mutex`. In addition, all servers must be communicating with the same central db/cache server.

Below shows the redis mutex demo:

```php
'components' => [
    'mutex' => [
        'class' => 'yii\redis\Mutex',
        'redis' => [
            'hostname' => 'localhost',
            'port' => 6379,
            'database' => 0,
        ]
    ],
],
```

```php
$schedule->command('report:generate')
                ->fridays()
                ->at('17:00')
                ->onOneServer();
```

How to use this extension in your application?
----------------------------------------------

You should create the following file under `@console/config/schedule.php` (note: you can create file with any name
and extension and anywere on your server, simpli ajust the name of the scheduleFile in the command below):

```php
<?php
/**
 * @var \omnilight\scheduling\Schedule $schedule
 */

// Place here all of your cron jobs

// This command will execute ls command every five minutes
$schedule->exec('ls')->everyFiveMinutes();

// This command will execute migration command of your application every hour
$schedule->command('migrate')->hourly();

// This command will call callback function every day at 10:00
$schedule->call(function(\yii\console\Application $app) {
    // Some code here...
})->dailyAt('10:00');

```

Next your should add the following command to your crontab:
```
* * * * * php /path/to/yii yii schedule/run --scheduleFile=@console/config/schedule.php 1>> /dev/null 2>&1
```

That's all! Now all your cronjobs will be runned as configured in your schedule.php file.

How to use this extension in your own extension?
------------------------------------------------

First of all, you should include dependency to the `omnilight\yii2-scheduling` into your composer.json:

```
...
'require': {
    "omnilight/yii2-schedule": "*"
}
...
```

Next you should create bootstrapping class for your extension, as described in the http://www.yiiframework.com/doc-2.0/guide-structure-extensions.html#bootstrapping-classes

Place into your bootstrapping method the following code:

```php
public function bootstrap(Application $app)
{
    if ($app instanceof \yii\console\Application) {
        if ($app->has('schedule')) {
            /** @var omnilight\scheduling\Schedule $schedule */
            $schedule = $app->get('schedule');
            // Place all your shedule command below
            $schedule->command('my-extension-command')->dailyAt('12:00');
        }
    }
}
```

Add to the README of your extension info for the user to register `schedule` component for the application
and add `schedule/run` command to the crontab as described upper.

Using `schedule` component
--------------------------

You do not have to use `schedule` component directly or define it in your application if you use schedule only in your application (and do not want to give ability for extensions to register they own cron jobs). But if you what to give extensions ability to register cronjobs, you should define `schedule` component in the application config:

```php
'schedule' => 'omnilight\scheduling\Schedule',
```

Using addition functions
------------------------

If you want to use `thenPing` method of the Event, you should add the following string to the `composer.json` of your app:
```
"guzzlehttp/guzzle": "~5.0"
```

Note about timezones
--------------------

Please note, that this is PHP extension, so it use timezone defined in php config or in your Yii's configuration file,
so set them correctly.


================================================
FILE: composer.json
================================================
{
  "name": "omnilight/yii2-scheduling",
  "description": "Scheduling extension for Yii2 framework",
  "keywords": [
    "yii",
    "scheduling",
    "cron"
  ],
  "minimum-stability": "dev",
  "authors": [
    {
      "name": "Pavel Agalecky",
      "email": "pavel.agalecky@gmail.com"
    }
  ],
  "type": "yii2-extension",
  "require": {
    "php": ">=5.4.0",
    "yiisoft/yii2": "2.0.*",
    "symfony/process": "2.6.* || 3.* || 4.* || ^5.0",
    "dragonmantank/cron-expression": "1.*"
  },
  "require-dev": {
    "phpunit/phpunit": "4.8.36"
  },
  "suggest": {
    "guzzlehttp/guzzle": "Required to use the thenPing method on schedules (~5.0)."
  },
  "autoload": {
    "psr-4": {
      "omnilight\\scheduling\\": "src"
    }
  },
  "extra": {
    "bootstrap": "omnilight\\scheduling\\Bootstrap"
  },
  "scripts": {
    "test": "vendor/bin/phpunit"
  },
  "config": {
    "allow-plugins": {
      "yiisoft/yii2-composer": true
    }
  }
}


================================================
FILE: docker-compose.yml
================================================
version: '3'

services:
  app:
    build: .


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         beStrictAboutTestsThatDoNotTestAnything="false"
         bootstrap="tests/bootstrap.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnError="false"
         stopOnFailure="false"
         verbose="true"
>
    <testsuites>
        <testsuite name="omnilight/yii2-scheduling test suite">
            <directory suffix="Test.php">./tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

================================================
FILE: src/Bootstrap.php
================================================
<?php

namespace omnilight\scheduling;
use yii\base\BootstrapInterface;
use yii\base\Application;
use yii\di\Instance;


/**
 * Class Bootstrap
 */
class Bootstrap implements BootstrapInterface
{

    /**
     * Bootstrap method to be called during application bootstrap stage.
     * @param Application $app the application currently running
     */
    public function bootstrap($app)
    {
        if ($app instanceof \yii\console\Application) {
            if (!isset($app->controllerMap['schedule'])) {
                $app->controllerMap['schedule'] = 'omnilight\scheduling\ScheduleController';
            }
        }
    }
}

================================================
FILE: src/CallbackEvent.php
================================================
<?php

namespace omnilight\scheduling;
use Yii;
use yii\base\Application;
use yii\base\InvalidParamException;
use yii\mutex\Mutex;

/**
 * Class CallbackEvent
 */
class CallbackEvent extends Event
{
    /**
     * The callback to call.
     *
     * @var string
     */
    protected $callback;
    /**
     * The parameters to pass to the method.
     *
     * @var array
     */
    protected $parameters;

    /**
     * Create a new event instance.
     *
     * @param Mutex $mutex
     * @param string $callback
     * @param array $parameters
     * @param array $config
     * @throws InvalidParamException
     */
    public function __construct(Mutex $mutex, $callback, array $parameters = [], $config = [])
    {
        $this->callback = $callback;
        $this->parameters = $parameters;
        $this->_mutex = $mutex;

        if (!empty($config)) {
            Yii::configure($this, $config);
        }

        if ( ! is_string($this->callback) && ! is_callable($this->callback))
        {
            throw new InvalidParamException(
                "Invalid scheduled callback event. Must be string or callable."
            );
        }
    }

    /**
     * Run the given event.
     *
     * @param Application $app
     * @return mixed
     */
    public function run(Application $app)
    {
        $this->trigger(self::EVENT_BEFORE_RUN);
        $response = call_user_func_array($this->callback, array_merge($this->parameters, [$app]));
        parent::callAfterCallbacks($app);
        $this->trigger(self::EVENT_AFTER_RUN);
        return $response;
    }

    /**
     * Do not allow the event to overlap each other.
     *
     * @return $this
     * @throws InvalidParamException
     */
    public function withoutOverlapping()
    {
        if (empty($this->_description)) {
            throw new InvalidParamException(
                "A scheduled event name is required to prevent overlapping. Use the 'description' method before 'withoutOverlapping'."
            );
        }

        return parent::withoutOverlapping();
    }

    /**
     * Get the mutex name for the scheduled command.
     *
     * @return string
     */
    protected function mutexName()
    {
        return 'framework/schedule-' . sha1($this->_description);
    }

    /**
     * Get the summary of the event for display.
     *
     * @return string
     */
    public function getSummaryForDisplay()
    {
        if (is_string($this->_description)) return $this->_description;
        return is_string($this->callback) ? $this->callback : 'Closure';
    }

}


================================================
FILE: src/Event.php
================================================
<?php

namespace omnilight\scheduling;

use Cron\CronExpression;
use GuzzleHttp\Client as HttpClient;
use Symfony\Component\Process\Process;
use yii\base\Application;
use yii\base\Component;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
use yii\mail\MailerInterface;
use yii\mutex\Mutex;
use yii\mutex\FileMutex;

/**
 * Class Event
 */
class Event extends Component
{
    const EVENT_BEFORE_RUN = 'beforeRun';
    const EVENT_AFTER_RUN = 'afterRun';

    /**
     * Command string
     * @var string
     */
    public $command;
    /**
     * The cron expression representing the event's frequency.
     *
     * @var string
     */
    protected $_expression = '* * * * * *';
    /**
     * The timezone the date should be evaluated on.
     *
     * @var \DateTimeZone|string
     */
    protected $_timezone;
    /**
     * The user the command should run as.
     *
     * @var string
     */
    protected $_user;
    /**
     * The filter callback.
     *
     * @var \Closure
     */
    protected $_filter;
    /**
     * The reject callback.
     *
     * @var \Closure
     */
    protected $_reject;
    /**
     * The location that output should be sent to.
     *
     * @var string
     */
    protected $_output = null;
    /**
     * The string for redirection.
     *
     * @var array
     */
    protected $_redirect = ' > ';
    /**
     * The array of callbacks to be run after the event is finished.
     *
     * @var array
     */
    protected $_afterCallbacks = [];
    /**
     * The human readable description of the event.
     *
     * @var string
     */
    protected $_description;
    /**
     * The mutex implementation.
     *
     * @var \yii\mutex\Mutex
     */
    protected $_mutex;

    /**
     * Decide if errors will be displayed.
     *
     * @var bool
     */
    protected $_omitErrors = false;

    /**
     * Create a new event instance.
     *
     * @param Mutex $mutex
     * @param string $command
     * @param array $config
     */
    public function __construct(Mutex $mutex, $command, $config = [])
    {
        $this->command = $command;
        $this->_mutex = $mutex;
        $this->_output = $this->getDefaultOutput();
        parent::__construct($config);
    }

    /**
     * Run the given event.
     * @param Application $app
     */
    public function run(Application $app)
    {
        $this->trigger(self::EVENT_BEFORE_RUN);
        if (count($this->_afterCallbacks) > 0) {
            $this->runCommandInForeground($app);
        } else {
            $this->runCommandInBackground($app);
        }
        $this->trigger(self::EVENT_AFTER_RUN);
    }

    /**
     * Get the mutex name for the scheduled command.
     *
     * @return string
     */
    protected function mutexName()
    {
        return 'framework/schedule-' . sha1($this->_expression . $this->command);
    }

    /**
     * Run the command in the foreground.
     *
     * @param Application $app
     */
    protected function runCommandInForeground(Application $app)
    {
        $command = trim($this->buildCommand(), '& ');
        $cwd = dirname($app->request->getScriptFile());
        if (method_exists(Process::class, 'fromShellCommandline')) {
            $process = Process::fromShellCommandline($command, $cwd, null, null, null);
        }
        else {
            $process = (new Process($command, $cwd, null, null, null));
        }
        $process->run();
        $this->callAfterCallbacks($app);
    }

    /**
     * Build the comand string.
     *
     * @return string
     */
    public function buildCommand()
    {
        $command = $this->command . $this->_redirect . $this->_output . (($this->_omitErrors) ? ' 2>&1 &' : ' &');
        return $this->_user ? 'sudo -u ' . $this->_user . ' ' . $command : $command;
    }

    /**
     * Call all of the "after" callbacks for the event.
     *
     * @param Application $app
     */
    protected function callAfterCallbacks(Application $app)
    {
        foreach ($this->_afterCallbacks as $callback) {
            call_user_func($callback, $app);
        }
    }

    /**
     * Run the command in the background using exec.
     *
     * @param Application $app
     */
    protected function runCommandInBackground(Application $app)
    {
        chdir(dirname($app->request->getScriptFile()));
        exec($this->buildCommand());
    }

    /**
     * Determine if the given event should run based on the Cron expression.
     *
     * @param Application $app
     * @return bool
     */
    public function isDue(Application $app)
    {
        return $this->expressionPasses() && $this->filtersPass($app);
    }

    /**
     * Determine if the Cron expression passes.
     *
     * @return bool
     */
    protected function expressionPasses()
    {
        $date = new \DateTime('now');
        if ($this->_timezone) {
            $date->setTimezone($this->_timezone);
        }
        return CronExpression::factory($this->_expression)->isDue($date);
    }

    /**
     * Determine if the filters pass for the event.
     *
     * @param Application $app
     * @return bool
     */
    protected function filtersPass(Application $app)
    {
        if ($this->_filter && !call_user_func($this->_filter, $app) ||
            $this->_reject && call_user_func($this->_reject, $app)
        ) {
            return false;
        }
        return true;
    }

    /**
     * Schedule the event to run hourly.
     *
     * @return $this
     */
    public function hourly()
    {
        return $this->cron('0 * * * * *');
    }

    /**
     * The Cron expression representing the event's frequency.
     *
     * @param  string $expression
     * @return $this
     */
    public function cron($expression)
    {
        $this->_expression = $expression;
        return $this;
    }

    /**
     * Schedule the event to run daily.
     *
     * @return $this
     */
    public function daily()
    {
        return $this->cron('0 0 * * * *');
    }

    /**
     * Schedule the command at a given time.
     *
     * @param  string $time
     * @return $this
     */
    public function at($time)
    {
        return $this->dailyAt($time);
    }

    /**
     * Schedule the event to run daily at a given time (10:00, 19:30, etc).
     *
     * @param  string $time
     * @return $this
     */
    public function dailyAt($time)
    {
        $segments = explode(':', $time);
        return $this->spliceIntoPosition(2, (int)$segments[0])
            ->spliceIntoPosition(1, count($segments) == 2 ? (int)$segments[1] : '0');
    }

    /**
     * Splice the given value into the given position of the expression.
     *
     * @param  int $position
     * @param  string $value
     * @return Event
     */
    protected function spliceIntoPosition($position, $value)
    {
        $segments = explode(' ', $this->_expression);
        $segments[$position - 1] = $value;
        return $this->cron(implode(' ', $segments));
    }

    /**
     * Schedule the event to run twice daily.
     *
     * @return $this
     */
    public function twiceDaily()
    {
        return $this->cron('0 1,13 * * * *');
    }

    /**
     * Schedule the event to run only on weekdays.
     *
     * @return $this
     */
    public function weekdays()
    {
        return $this->spliceIntoPosition(5, '1-5');
    }

    /**
     * Schedule the event to run only on Mondays.
     *
     * @return $this
     */
    public function mondays()
    {
        return $this->days(1);
    }

    /**
     * Set the days of the week the command should run on.
     *
     * @param  array|int $days
     * @return $this
     */
    public function days($days)
    {
        $days = is_array($days) ? $days : func_get_args();
        return $this->spliceIntoPosition(5, implode(',', $days));
    }

    /**
     * Schedule the event to run only on Tuesdays.
     *
     * @return $this
     */
    public function tuesdays()
    {
        return $this->days(2);
    }

    /**
     * Schedule the event to run only on Wednesdays.
     *
     * @return $this
     */
    public function wednesdays()
    {
        return $this->days(3);
    }

    /**
     * Schedule the event to run only on Thursdays.
     *
     * @return $this
     */
    public function thursdays()
    {
        return $this->days(4);
    }

    /**
     * Schedule the event to run only on Fridays.
     *
     * @return $this
     */
    public function fridays()
    {
        return $this->days(5);
    }

    /**
     * Schedule the event to run only on Saturdays.
     *
     * @return $this
     */
    public function saturdays()
    {
        return $this->days(6);
    }

    /**
     * Schedule the event to run only on Sundays.
     *
     * @return $this
     */
    public function sundays()
    {
        return $this->days(0);
    }

    /**
     * Schedule the event to run weekly.
     *
     * @return $this
     */
    public function weekly()
    {
        return $this->cron('0 0 * * 0 *');
    }

    /**
     * Schedule the event to run weekly on a given day and time.
     *
     * @param  int $day
     * @param  string $time
     * @return $this
     */
    public function weeklyOn($day, $time = '0:0')
    {
        $this->dailyAt($time);
        return $this->spliceIntoPosition(5, $day);
    }

    /**
     * Schedule the event to run monthly.
     *
     * @return $this
     */
    public function monthly()
    {
        return $this->cron('0 0 1 * * *');
    }

    /**
     * Schedule the event to run yearly.
     *
     * @return $this
     */
    public function yearly()
    {
        return $this->cron('0 0 1 1 * *');
    }

    /**
     * Schedule the event to run every minute.
     *
     * @return $this
     */
    public function everyMinute()
    {
        return $this->cron('* * * * * *');
    }

    /**
     * Schedule the event to run every N minutes.
     *
     * @param int|string $minutes
     * @return $this
     */
    public function everyNMinutes($minutes)
    {
        return $this->cron('*/'.$minutes.' * * * * *');
    }

    /**
     * Schedule the event to run every five minutes.
     *
     * @return $this
     */
    public function everyFiveMinutes()
    {
        return $this->everyNMinutes(5);
    }

    /**
     * Schedule the event to run every ten minutes.
     *
     * @return $this
     */
    public function everyTenMinutes()
    {
        return $this->everyNMinutes(10);
    }

    /**
     * Schedule the event to run every thirty minutes.
     *
     * @return $this
     */
    public function everyThirtyMinutes()
    {
        return $this->cron('0,30 * * * * *');
    }

    /**
     * Set the timezone the date should be evaluated on.
     *
     * @param  \DateTimeZone|string $timezone
     * @return $this
     */
    public function timezone($timezone)
    {
        $this->_timezone = $timezone;
        return $this;
    }

    /**
     * Set which user the command should run as.
     *
     * @param  string $user
     * @return $this
     */
    public function user($user)
    {
        $this->_user = $user;
        return $this;
    }

    /**
     * Set if errors should be displayed
     *
     * @param  bool $omitErrors
     * @return $this
     */
    public function omitErrors($omitErrors = false)
    {
        $this->_omitErrors = $omitErrors;
        return $this;
    }

    /**
     * Do not allow the event to overlap each other.
     *
     * @return $this
     */
    public function withoutOverlapping()
    {
        return $this->then(function() {
            $this->_mutex->release($this->mutexName());
        })->skip(function() {
            return !$this->_mutex->acquire($this->mutexName());
        });
    }

    /**
     * Allow the event to only run on one server for each cron expression.
     *
     * @return $this
     */
    public function onOneServer()
    {
        if ($this->_mutex instanceof FileMutex) {
            throw new InvalidConfigException('You must config mutex in the application component, except the FileMutex.');
        }

        return $this->withoutOverlapping();
    }

    /**
     * Register a callback to further filter the schedule.
     *
     * @param  \Closure $callback
     * @return $this
     */
    public function when(\Closure $callback)
    {
        $this->_filter = $callback;
        return $this;
    }

    /**
     * Register a callback to further filter the schedule.
     *
     * @param  \Closure $callback
     * @return $this
     */
    public function skip(\Closure $callback)
    {
        $this->_reject = $callback;
        return $this;
    }

    /**
     * Send the output of the command to a given location.
     *
     * @param  string $location
     * @return $this
     */
    public function sendOutputTo($location)
    {
        $this->_redirect = ' > ';
        $this->_output = $location;
        return $this;
    }

    /**
     * Append the output of the command to a given location.
     *
     * @param  string $location
     * @return $this
     */
    public function appendOutputTo($location)
    {
        $this->_redirect = ' >> ';
        $this->_output = $location;
        return $this;
    }

    /**
     * E-mail the results of the scheduled operation.
     *
     * @param  array $addresses
     * @return $this
     *
     * @throws \LogicException
     */
    public function emailOutputTo($addresses)
    {
        if (is_null($this->_output) || $this->_output == $this->getDefaultOutput()) {
            throw new InvalidCallException("Must direct output to a file in order to e-mail results.");
        }
        $addresses = is_array($addresses) ? $addresses : func_get_args();
        return $this->then(function (Application $app) use ($addresses) {
            $this->emailOutput($app->mailer, $addresses);
        });
    }

    /**
     * Register a callback to be called after the operation.
     *
     * @param  \Closure $callback
     * @return $this
     */
    public function then(\Closure $callback)
    {
        $this->_afterCallbacks[] = $callback;
        return $this;
    }

    /**
     * E-mail the output of the event to the recipients.
     *
     * @param MailerInterface $mailer
     * @param  array $addresses
     */
    protected function emailOutput(MailerInterface $mailer, $addresses)
    {
        $textBody = file_get_contents($this->_output);

        if (trim($textBody) != '' ) {
            $mailer->compose()
                ->setTextBody($textBody)
                ->setSubject($this->getEmailSubject())
                ->setTo($addresses)
                ->send();
        }
    }

    /**
     * Get the e-mail subject line for output results.
     *
     * @return string
     */
    protected function getEmailSubject()
    {
        if ($this->_description) {
            return 'Scheduled Job Output (' . $this->_description . ')';
        }
        return 'Scheduled Job Output';
    }

    /**
     * Register a callback to the ping a given URL after the job runs.
     *
     * @param  string $url
     * @return $this
     */
    public function thenPing($url)
    {
        return $this->then(function () use ($url) {
            (new HttpClient)->get($url);
        });
    }

    /**
     * Set the human-friendly description of the event.
     *
     * @param  string $description
     * @return $this
     */
    public function description($description)
    {
        $this->_description = $description;
        return $this;
    }

    /**
     * Get the summary of the event for display.
     *
     * @return string
     */
    public function getSummaryForDisplay()
    {
        if (is_string($this->_description)) return $this->_description;
        return $this->buildCommand();
    }

    /**
     * Get the Cron expression for the event.
     *
     * @return string
     */
    public function getExpression()
    {
        return $this->_expression;
    }

    public function getDefaultOutput()
    {
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            return 'NUL';
        } else {
            return '/dev/null';
        }
    }
}


================================================
FILE: src/Schedule.php
================================================
<?php

namespace omnilight\scheduling;

use Yii;
use yii\base\Component;
use yii\base\Application;
use yii\mutex\FileMutex;


/**
 * Class Schedule
 */
class Schedule extends Component
{
    /**
     * All of the events on the schedule.
     *
     * @var Event[]
     */
    protected $_events = [];

    /**
     * The mutex implementation.
     *
     * @var \yii\mutex\Mutex
     */
    protected $_mutex;

    /**
     * @var string The name of cli script
     */
    public $cliScriptName = 'yii';

    /**
     * Schedule constructor.
     * @param array $config
     */
    public function __construct(array $config = [])
    {
        $this->_mutex = Yii::$app->has('mutex') ? Yii::$app->get('mutex') : (new FileMutex());

        parent::__construct($config);
    }

    /**
     * Add a new callback event to the schedule.
     *
     * @param  string  $callback
     * @param  array   $parameters
     * @return Event
     */
    public function call($callback, array $parameters = array())
    {
        $this->_events[] = $event = new CallbackEvent($this->_mutex, $callback, $parameters);
        return $event;
    }
    /**
     * Add a new cli command event to the schedule.
     *
     * @param  string  $command
     * @return Event
     */
    public function command($command)
    {
        return $this->exec(PHP_BINARY . ' ' . $this->cliScriptName . ' ' . $command);
    }

    /**
     * Add a new command event to the schedule.
     *
     * @param  string  $command
     * @return Event
     */
    public function exec($command)
    {
        $this->_events[] = $event = new Event($this->_mutex, $command);
        return $event;
    }

    public function getEvents()
    {
        return $this->_events;
    }

    /**
     * Get all of the events on the schedule that are due.
     *
     * @param \yii\base\Application $app
     * @return Event[]
     */
    public function dueEvents(Application $app)
    {
        return array_filter($this->_events, function(Event $event) use ($app)
        {
            return $event->isDue($app);
        });
    }
}


================================================
FILE: src/ScheduleController.php
================================================
<?php

namespace omnilight\scheduling;
use yii\console\Controller;
use yii\di\Instance;


/**
 * Run the scheduled commands
 */
class ScheduleController extends Controller
{
    /**
     * @var Schedule
     */
    public $schedule = 'schedule';
    /**
     * @var string Schedule file that will be used to run schedule
     */
    public $scheduleFile;

    /**
     * @var bool set to true to avoid error output
     */
    public $omitErrors = false;

    public function options($actionID)
    {
        return array_merge(parent::options($actionID),
            $actionID == 'run' ? ['scheduleFile', 'omitErrors'] : []
        );
    }


    public function init()
    {
        if (\Yii::$app->has($this->schedule)) {
            $this->schedule = Instance::ensure($this->schedule, Schedule::className());
        } else {
            $this->schedule = \Yii::createObject(Schedule::className());
        }
        parent::init();
    }


    public function actionRun()
    {
        $this->importScheduleFile();

        $events = $this->schedule->dueEvents(\Yii::$app);

        foreach ($events as $event) {
            $event->omitErrors($this->omitErrors);
            $this->stdout('Running scheduled command: '.$event->getSummaryForDisplay()."\n");
            $event->run(\Yii::$app);
        }

        if (count($events) === 0)
        {
            $this->stdout("No scheduled commands are ready to run.\n");
        }
    }

    protected function importScheduleFile()
    {
        if ($this->scheduleFile === null) {
            return;
        }

        $scheduleFile = \Yii::getAlias($this->scheduleFile);
        if (file_exists($scheduleFile) == false) {
            $this->stderr('Can not load schedule file '.$this->scheduleFile."\n");
            return;
        }

        $schedule = $this->schedule;
        call_user_func(function() use ($schedule, $scheduleFile) {
            include $scheduleFile;
        });
    }
}

================================================
FILE: tests/EventTest.php
================================================
<?php

namespace omnilight\scheduling\Tests;

use omnilight\scheduling\Event;
use yii\mutex\Mutex;

class EventTest extends \PHPUnit_Framework_TestCase
{
    public function buildCommandData()
    {
        return [
            [false, 'php -i', '/dev/null', 'php -i > /dev/null'],
            [false, 'php -i', '/my folder/foo.log', 'php -i > /my folder/foo.log'],
            [true, 'php -i', '/dev/null', 'php -i > /dev/null 2>&1 &'],
            [true, 'php -i', '/my folder/foo.log', 'php -i > /my folder/foo.log 2>&1 &'],
        ];
    }

    /**
     * @dataProvider buildCommandData
     * @param bool $omitErrors
     * @param string $command
     * @param string $outputTo
     * @param string $result
     */
    public function testBuildCommandSendOutputTo($omitErrors, $command, $outputTo, $result)
    {
        $event = new Event($this->getMock(Mutex::className()), $command);
        $event->omitErrors($omitErrors);
        $event->sendOutputTo($outputTo);
        $this->assertSame($result, $event->buildCommand());
    }
}


================================================
FILE: tests/bootstrap.php
================================================
<?php

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

date_default_timezone_set('UTC');
Download .txt
gitextract_zj2jjtom/

├── .dockerignore
├── .github/
│   └── workflows/
│       └── php.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── docker-compose.yml
├── phpunit.xml.dist
├── src/
│   ├── Bootstrap.php
│   ├── CallbackEvent.php
│   ├── Event.php
│   ├── Schedule.php
│   └── ScheduleController.php
└── tests/
    ├── EventTest.php
    └── bootstrap.php
Download .txt
SYMBOL INDEX (77 symbols across 6 files)

FILE: src/Bootstrap.php
  class Bootstrap (line 12) | class Bootstrap implements BootstrapInterface
    method bootstrap (line 19) | public function bootstrap($app)

FILE: src/CallbackEvent.php
  class CallbackEvent (line 12) | class CallbackEvent extends Event
    method __construct (line 36) | public function __construct(Mutex $mutex, $callback, array $parameters...
    method run (line 60) | public function run(Application $app)
    method withoutOverlapping (line 75) | public function withoutOverlapping()
    method mutexName (line 91) | protected function mutexName()
    method getSummaryForDisplay (line 101) | public function getSummaryForDisplay()

FILE: src/Event.php
  class Event (line 19) | class Event extends Component
    method __construct (line 104) | public function __construct(Mutex $mutex, $command, $config = [])
    method run (line 116) | public function run(Application $app)
    method mutexName (line 132) | protected function mutexName()
    method runCommandInForeground (line 142) | protected function runCommandInForeground(Application $app)
    method buildCommand (line 161) | public function buildCommand()
    method callAfterCallbacks (line 172) | protected function callAfterCallbacks(Application $app)
    method runCommandInBackground (line 184) | protected function runCommandInBackground(Application $app)
    method isDue (line 196) | public function isDue(Application $app)
    method expressionPasses (line 206) | protected function expressionPasses()
    method filtersPass (line 221) | protected function filtersPass(Application $app)
    method hourly (line 236) | public function hourly()
    method cron (line 247) | public function cron($expression)
    method daily (line 258) | public function daily()
    method at (line 269) | public function at($time)
    method dailyAt (line 280) | public function dailyAt($time)
    method spliceIntoPosition (line 294) | protected function spliceIntoPosition($position, $value)
    method twiceDaily (line 306) | public function twiceDaily()
    method weekdays (line 316) | public function weekdays()
    method mondays (line 326) | public function mondays()
    method days (line 337) | public function days($days)
    method tuesdays (line 348) | public function tuesdays()
    method wednesdays (line 358) | public function wednesdays()
    method thursdays (line 368) | public function thursdays()
    method fridays (line 378) | public function fridays()
    method saturdays (line 388) | public function saturdays()
    method sundays (line 398) | public function sundays()
    method weekly (line 408) | public function weekly()
    method weeklyOn (line 420) | public function weeklyOn($day, $time = '0:0')
    method monthly (line 431) | public function monthly()
    method yearly (line 441) | public function yearly()
    method everyMinute (line 451) | public function everyMinute()
    method everyNMinutes (line 462) | public function everyNMinutes($minutes)
    method everyFiveMinutes (line 472) | public function everyFiveMinutes()
    method everyTenMinutes (line 482) | public function everyTenMinutes()
    method everyThirtyMinutes (line 492) | public function everyThirtyMinutes()
    method timezone (line 503) | public function timezone($timezone)
    method user (line 515) | public function user($user)
    method omitErrors (line 527) | public function omitErrors($omitErrors = false)
    method withoutOverlapping (line 538) | public function withoutOverlapping()
    method onOneServer (line 552) | public function onOneServer()
    method when (line 567) | public function when(\Closure $callback)
    method skip (line 579) | public function skip(\Closure $callback)
    method sendOutputTo (line 591) | public function sendOutputTo($location)
    method appendOutputTo (line 604) | public function appendOutputTo($location)
    method emailOutputTo (line 619) | public function emailOutputTo($addresses)
    method then (line 636) | public function then(\Closure $callback)
    method emailOutput (line 648) | protected function emailOutput(MailerInterface $mailer, $addresses)
    method getEmailSubject (line 666) | protected function getEmailSubject()
    method thenPing (line 680) | public function thenPing($url)
    method description (line 693) | public function description($description)
    method getSummaryForDisplay (line 704) | public function getSummaryForDisplay()
    method getExpression (line 715) | public function getExpression()
    method getDefaultOutput (line 720) | public function getDefaultOutput()

FILE: src/Schedule.php
  class Schedule (line 14) | class Schedule extends Component
    method __construct (line 39) | public function __construct(array $config = [])
    method call (line 53) | public function call($callback, array $parameters = array())
    method command (line 64) | public function command($command)
    method exec (line 75) | public function exec($command)
    method getEvents (line 81) | public function getEvents()
    method dueEvents (line 92) | public function dueEvents(Application $app)

FILE: src/ScheduleController.php
  class ScheduleController (line 11) | class ScheduleController extends Controller
    method options (line 27) | public function options($actionID)
    method init (line 35) | public function init()
    method actionRun (line 46) | public function actionRun()
    method importScheduleFile (line 64) | protected function importScheduleFile()

FILE: tests/EventTest.php
  class EventTest (line 8) | class EventTest extends \PHPUnit_Framework_TestCase
    method buildCommandData (line 10) | public function buildCommandData()
    method testBuildCommandSendOutputTo (line 27) | public function testBuildCommandSendOutputTo($omitErrors, $command, $o...
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": ".dockerignore",
    "chars": 89,
    "preview": "vendor\n.idea\n.dockerignore\n.git*\n.travis.yml\ncomposer.lock\ndocker-compose.yml\nDockerfile\n"
  },
  {
    "path": ".github/workflows/php.yml",
    "chars": 549,
    "preview": "name: PHP Composer\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    r"
  },
  {
    "path": ".gitignore",
    "chars": 27,
    "preview": ".idea\nvendor\ncomposer.lock\n"
  },
  {
    "path": ".travis.yml",
    "chars": 384,
    "preview": "language: php\n\nphp:\n  - 5.4\n\nenv:\n  global:\n    - DEFAULT_COMPOSER_FLAGS=\"--prefer-dist --no-interaction --no-progress -"
  },
  {
    "path": "Dockerfile",
    "chars": 361,
    "preview": "FROM composer\n\nFROM php:5.4-cli\n\nRUN apt-get update && \\\n    apt-get install -y \\\n        git \\\n        unzip\n\nRUN docke"
  },
  {
    "path": "LICENSE",
    "chars": 1098,
    "preview": "MIT License\n\nCopyright (c) 2015 Pavel Agalecky <pavel.agalecky@gmail.com>\n\nPermission is hereby granted, free of charge,"
  },
  {
    "path": "Makefile",
    "chars": 148,
    "preview": "docker_build = (docker-compose build)\n\nbuild:\n\t$(call docker_build)\n\ntest:\n\t$(call docker_build) && docker-compose run a"
  },
  {
    "path": "README.md",
    "chars": 7009,
    "preview": "Schedule extension for Yii2\n===========================\n\nThis extension is the port of Laravel's Schedule component (htt"
  },
  {
    "path": "composer.json",
    "chars": 943,
    "preview": "{\n  \"name\": \"omnilight/yii2-scheduling\",\n  \"description\": \"Scheduling extension for Yii2 framework\",\n  \"keywords\": [\n   "
  },
  {
    "path": "docker-compose.yml",
    "chars": 44,
    "preview": "version: '3'\n\nservices:\n  app:\n    build: .\n"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 671,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         b"
  },
  {
    "path": "src/Bootstrap.php",
    "chars": 632,
    "preview": "<?php\n\nnamespace omnilight\\scheduling;\nuse yii\\base\\BootstrapInterface;\nuse yii\\base\\Application;\nuse yii\\di\\Instance;\n\n"
  },
  {
    "path": "src/CallbackEvent.php",
    "chars": 2575,
    "preview": "<?php\n\nnamespace omnilight\\scheduling;\nuse Yii;\nuse yii\\base\\Application;\nuse yii\\base\\InvalidParamException;\nuse yii\\mu"
  },
  {
    "path": "src/Event.php",
    "chars": 16166,
    "preview": "<?php\n\nnamespace omnilight\\scheduling;\n\nuse Cron\\CronExpression;\nuse GuzzleHttp\\Client as HttpClient;\nuse Symfony\\Compon"
  },
  {
    "path": "src/Schedule.php",
    "chars": 2088,
    "preview": "<?php\n\nnamespace omnilight\\scheduling;\n\nuse Yii;\nuse yii\\base\\Component;\nuse yii\\base\\Application;\nuse yii\\mutex\\FileMut"
  },
  {
    "path": "src/ScheduleController.php",
    "chars": 1953,
    "preview": "<?php\n\nnamespace omnilight\\scheduling;\nuse yii\\console\\Controller;\nuse yii\\di\\Instance;\n\n\n/**\n * Run the scheduled comma"
  },
  {
    "path": "tests/EventTest.php",
    "chars": 1043,
    "preview": "<?php\n\nnamespace omnilight\\scheduling\\Tests;\n\nuse omnilight\\scheduling\\Event;\nuse yii\\mutex\\Mutex;\n\nclass EventTest exte"
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 87,
    "preview": "<?php\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\ndate_default_timezone_set('UTC');\n"
  }
]

About this extraction

This page contains the full source code of the omnilight/yii2-scheduling GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (35.0 KB), approximately 9.2k tokens, and a symbol index with 77 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!