Repository URL to install this package:
|
Version:
2.2.0 ▾
|
| .idea |
| examples |
| scripts |
| src |
| tests |
| .gitignore |
| .travis.yml |
| README.md |
| phpunit.xml |
| composer.json |
Toolkit to handle running commandline tasks from PHP7+.
Run composer require crazyfactory/jobs to install the latest version into your composer powered project.
The JobManager-class manages your configurations and runs your tasks. It can be configured with plain arrays or instances of JobConfig.
Running a JobConfig will return a JobResult. You can add ResultProcessor- and RuntimeProcessor-instances to it to be called automatically whenever you're running a JobConfig.
For working examples have a look at the /examples-folder.
Create an instance of JobManager, add a JobConfig and run it!
// instantiate a JobManager $jobManager = new JobManager(); // create a JobConfig to run some arbitrary php file $jobConfig = new JobConfig([ 'name' => 'my-task', 'cmd' => 'php /my-absolute-path/my-task.php' ]); // add it to the manager $jobManager->withJob($jobConfig); // run! $jobManager->run('my-task');
Add multiple JobConfig instances at once
$jobManager->withJobs([ $jobConfigOne, $jobConfigTwo, $jobConfigThree ]);
You can also provide plain arrays, they will be converted on the fly.
$jobManager->withJobs([ ['name' => 'foo', 'cmd' => './bar'], ['name' => 'apple', 'cmd' => './pie'], ['name' => 'bonnie', 'cmd' => './clyde'] ]);
If it's an array and there's no name defined, the index will be used instead
$jobManager->withJobs([ 'foo' => ['cmd' => './bar'], 'apple' => ['cmd' => './pie'], 'bonnie' => ['cmd' => './clyde'] ]);
You can easily add your configuration from a .json-file.
$myConfig = json_decode(file_get_contents('my-jobs-config.json'), true); $jobManager->withJobs($myConfig);
Note that using withJobs() and/or withJob() multiple will overwrite jobs with the same name, but will not discard other jobs already present.
It's likely that you want to change the default options for all jobs. You can do this with withDefaultConfig().
$jobManager->withDefaultConfig(new JobConfig([ 'singleton' => true, ]);
When using withJobs() to add multiple jobs, by convention if a key matches the $treatKeyAsDefault-argument, it will be used as the default config for all configuration and not be added as a task.
You can adjust this by providing a different $treatKeyAsDefault-argument. Or pass null to deactivate this altogether.
$jobManager->withJobs([ 'default' => ['singleton' => true], $configOne, $configTwo, $configThree ]);
The JobManager is intended to be integrated into other systems (like a crontab schedule, a CLI-tool, etc.) and therefor offers two types of hooks to simplify integration.
A runtime processor is a class, which implements IJobRuntimeProcessor and will be called periodically during the execution of the script.
You can create any number of classes to work with the elapsed runtime, the time passed since the last call and the original JobConfig
class MyRuntimeProcessor implements IJobRuntimeProcessor { public function process(int $seconds, int $deltaSeconds, JobConfig $jobConfig) { $timeString = Format::secondsToTimeElapsedString($seconds); echo "\n[RUNTIME] running for {$seconds} seconds already...\n"; } }
Or send a warning to your fellow devs if a task takes longer than expected.
class SendMailRuntimeProcessor implements IJobRuntimeProcessor { protected $warningSent = false; public function process(int $seconds, int $deltaSeconds, JobConfig $jobConfig) { if ($seconds > 60 && !$this->warningSent) { sendmail('admin@example.com', $jobConfig->name . ' is running too long?!?'); $this->warningSent = true; } // still running? if ($seconds > 600) { // returning false from process() will quit the process! return false; } } }
Just add any number of runtime processors to your JobManager before calling run()
$jobManager->withRuntimeProcessor(new SendMailRuntimeProcessor()); $jobManager->withRuntimeProcessor(new MyRuntimeProcessor());
If you don't want to create a class, you can use SimpleRuntimeProcessor instead.
$jobManager->withRuntimeProcessor(new SimpleRuntimeProcessor(function ($s, $d, $jobConfig) { echo "\n[RUNTIME] running for {$seconds} seconds already...\n"; }));
A result processor is a class, which implements IJobResultProcessor and will be called after a job has finished execution.
By default any result is discarded and the exit code passed through, but it's a common scenario to log the output of the Job to a file for later review.
For this you can use the LogToDiskResultProcessor-class easily. Just provide a path to store the log files.
$jobManager->withResultProcessor(new LogToDiskResultProcessor('/my-app/log/jobs'));
We can create the directory for you if it helps :)
$resultProcessor = (new LogToDiskResultProcessor()) ->withEnsuredDirectory('/my-app/log/jobs'); $jobManager->withResultProcessor($resultProcessor);
Using Slack? How about sending a message to your team whenever a job crashed?
$jobManager->withResultProcessor(new SimpleResultProcessor(function($jR, $jC) { if ($jR->getExitCode() > 0) { $message = $jC->name . ' broke down after ' . $jR->duration . ' seconds!'; $mySlackClient->sendMessageToChannel('#dev', $message); } }));
The JobConfig provides some common properties which by default are unused, but can be used by processors like the LogToDiscResultProcessor.
logErrorlogSuccessreportErrorreportSuccessJobConfig implements ArrayAccess so you may also add any property you like via offsets and use them in your custom processors. If you prefer code-completion you can extend JobConfig and add additional @property-tags.
/** * @property int $myCustomProperty */ class MyCustomJobConfig extends JobConfig { }
This package does offer a hook to provide Singleton compatibility. Add a class implementing IJobLockProvider and add it with $jobManager->withLockProvider($instance).
Here's an example for a simple file-based lock provider
class MyLockProvider implements IJobLockProvider { protected $dir; protected $locks = []; public function __construct($dir) { $this->dir = $dir; } public function acquire($key) : bool { // Lock already acquired if (isset($this->>$locks[$key]) && self::$locks[$key]) { return false; } $path = self::getPath(); $filename = $this->dir . DIRECTORY_SEPARATOR . md5($key) . '.lock'; $res = fopen($filename, 'w'); if (!is_resource($res)) { throw new \Exception("File '$filename' cant be accessed"); } self::$locks[$key] = $res; return flock(self::$locks[$key], LOCK_EX + LOCK_NB); } public function release($key) : bool { // No Lock exists if (!isset(self::$locks[$key]) || !self::$locks[$key]) { return false; } flock(self::$locks[$key], LOCK_UN); unset(self::$locks[$key]); return true; } }
Just add it to your JobManager and update your JobConfigs or default config.
// Add provider $jobManager->withLockProvider(new MyLockProvider('/my-app/temp/lock')); // Set singleton => true as default $jobManager->withDefaultConfig(new JobConfig(['singleton => true']);
Now all your Jobs will run as singletons. You could also hook this to a database to manage locks for a clustered application, Neat!
The CI runner will take care of publishing after the build and tests where successful. To trigger this mechanism, just set a github tag with the new version number and push it as usual. Note: Please ensure that you push the tags to origin (github) first, otherwise the packaging service will not be able to find set the new version and therfor fail.