Async/src/FuzeWorks/Async/ShellWorker.php

215 lines
6.7 KiB
PHP

<?php
/**
* FuzeWorks Async Library
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2020 TechFuze
*
* 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.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2020, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Async;
use FuzeWorks\Async\Events\TaskHandleEvent;
use FuzeWorks\Event\HaltExecutionEvent;
use FuzeWorks\Events;
use FuzeWorks\Exception\EventException;
use FuzeWorks\Logger;
use FuzeWorks\Priority;
class ShellWorker
{
/**
* @var TaskStorage
*/
protected $taskStorage;
/**
* @var Task
*/
protected $task;
/**
* @var bool
*/
protected $post;
public function __construct(TaskStorage $taskStorage)
{
$this->taskStorage = $taskStorage;
Events::addListener([$this, 'fatalHandler'], 'haltExecutionEvent', Priority::HIGH);
}
/**
* Run a task by finding its ID
*
* @param string $taskId
* @param bool $post
* @throws EventException
* @throws TasksException
*/
public function runTaskById(string $taskId, bool $post = false)
{
// First fetch the task
try {
$task = $this->taskStorage->getTaskById($taskId);
} catch (TasksException $e) {
throw new TasksException("Could not run worker. Task not found.");
}
$this->run($task, $post);
}
/**
* @param Task $task
* @param bool $post
* @throws EventException
* @throws TasksException
*/
public function run(Task $task, bool $post = false)
{
// Fire a taskHandleEvent
/** @var TaskHandleEvent $event */
$event = Events::fireEvent(new TaskHandleEvent(), $task);
// Set task to this worker
$this->task = $event->getTask();
$this->post = $post;
// Fetch the callable
$handler = $this->task->getHandler();
// Execute the handler and all its parent handlers
$success = $this->executeHandler($this->task, $handler, $post);
// Fetch the output and errors
$output = $post ? $handler->getPostOutput() : $handler->getOutput();
$output = is_null($output) ? '' : $output;
$errors = $this->getErrors();
// If the task failed, write so to task storage, based on whether this is a post request or not
if (!$success && $post)
$this->taskStorage->writePostOutput($this->task, $output, $errors, Task::FAILED);
elseif (!$success && !$post)
$this->taskStorage->writeTaskOutput($this->task, $output, $errors, Task::FAILED);
elseif ($success && $post)
$this->taskStorage->writePostOutput($this->task, $output, $errors, Task::SUCCESS);
else
$this->taskStorage->writeTaskOutput($this->task, $output, $errors, Task::SUCCESS);
// And write the final output
$this->output((string) $output, $errors);
}
protected function executeHandler(Task $task, Handler $handler, bool $usePost = false): bool
{
// First check to see if there is a parent handler
$parent = $handler->getParentHandler();
if (!is_null($parent)) {
// Execute the parent
if ($this->executeHandler($task, $parent, $usePost) === false)
return false;
// Fetch the output of the parent
$output = $usePost ? $parent->getPostOutput() : $parent->getOutput();
// And insert it as input into the child handler
$handler->setParentInput($output);
}
return $usePost ? $handler->postHandler($task) : $handler->primaryHandler($task);
}
/**
* In case a fatal error or exception occurs, the errors shall be redirected to stderr
*
* @param HaltExecutionEvent $event
*/
public function fatalHandler(HaltExecutionEvent $event)
{
// Cancel further execution by FuzeWorks
$event->setCancelled(true);
// Collect all error logs
$errors = $this->getErrors();
$this->output('', $errors);
// If no task is set yet, abort error logging to task
if (is_null($this->task))
return;
try {
// Write to TaskStorage
if (!$this->post)
$this->taskStorage->writeTaskOutput($this->task, '', $errors, Task::FAILED);
else
$this->taskStorage->writePostOutput($this->task, '', $errors, Task::FAILED);
} catch (TasksException $e) {
// Ignore
}
exit;
}
/**
* Get all errors, exceptions and warnings from runtime and turn them into a string
*
* @return string
*/
protected function getErrors(): string
{
$output = [];
foreach (Logger::$logs as $log)
{
if ($log['type'] !== 'ERROR' && $log['type'] !== 'EXCEPTION' && $log['type'] !== 'WARNING')
continue;
$output[] = strtoupper($log['type']) . ' ' .
(!empty($log['logFile']) && !empty($log['logLine']) ? $log['logFile'] . ':' . $log['logLine'] . " '" : "'") . $log['message'] . "'";
}
return implode("\n", $output);
}
/**
* Output the results to stdout and stderr
*
* @param string $output
* @param string $errors
*/
protected function output(string $output, string $errors)
{
// First write to stderr
if (!empty($errors))
fwrite(STDERR, $errors . PHP_EOL);
// Afterwards write to stdout
if (!empty($output))
echo $output . PHP_EOL;
}
}