Files
2024-07-15 11:28:08 +02:00

392 lines
9.6 KiB
PHP

<?php
/**
* @package solo
* @copyright Copyright (c)2014-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 3 or later
*/
// Protection against direct access
defined('AKEEBAENGINE') or die();
/**
* The superclass of all Akeeba Engine parts. The "parts" are intelligent stateful
* classes which perform a single procedure and have preparation, running and
* finalization phases. The transition between phases is handled automatically by
* this superclass' tick() final public method, which should be the ONLY public API
* exposed to the rest of the Akeeba Engine.
*/
abstract class AliceAbstractPart extends AliceAbstractObject
{
/**
* Indicates whether this part has finished its initialisation cycle
* @var boolean
*/
protected $isPrepared = false;
/**
* Indicates whether this part has more work to do (it's in running state)
* @var boolean
*/
protected $isRunning = false;
/**
* Indicates whether this part has finished its finalization cycle
* @var boolean
*/
protected $isFinished = false;
/**
* Indicates whether this part has finished its run cycle
* @var boolean
*/
protected $hasRan = false;
/**
* The name of the engine part (a.k.a. Domain), used in return table
* generation.
* @var string
*/
protected $active_domain = "";
/**
* The step this engine part is in. Used verbatim in return table and
* should be set by the code in the _run() method.
* @var string
*/
protected $active_step = "";
/**
* A more detailed description of the step this engine part is in. Used
* verbatim in return table and should be set by the code in the _run()
* method.
* @var string
*/
protected $active_substep = "";
/**
* Any configuration variables, in the form of an array.
* @var array
*/
protected $_parametersArray = array();
/** @var string The database root key */
protected $databaseRoot = array();
/** @var int Last reported warnings's position in array */
private $warnings_pointer = -1;
/** @var bool Should we log the step nesting? */
protected $nest_logging = false;
/** @var object Embedded installer preferences */
protected $installerSettings;
/**
* Public constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Runs the preparation for this part. Should set _isPrepared
* to true
*/
abstract protected function _prepare();
/**
* Runs the finalisation process for this part. Should set
* _isFinished to true.
*/
abstract protected function _finalize();
/**
* Runs the main functionality loop for this part. Upon calling,
* should set the _isRunning to true. When it finished, should set
* the _hasRan to true. If an error is encountered, setError should
* be used.
*/
abstract protected function _run();
/**
* Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
* in fear of timing out.
*/
protected function setBreakFlag()
{
$registry = AliceFactory::getConfiguration();
$registry->set('volatile.breakflag', true);
}
/**
* Sets the engine part's internal state, in an easy to use manner
*
* @param string $state One of init, prepared, running, postrun, finished, error
* @param string $errorMessage The reported error message, should the state be set to error
*/
protected function setState($state = 'init', $errorMessage='Invalid setState argument')
{
switch($state)
{
case 'init':
$this->isPrepared = false;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'prepared':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'running':
$this->isPrepared = true;
$this->isRunning = true;
$this->isFinished = false;
$this->hasRun = false;
break;
case 'postrun':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = false;
$this->hasRun = true;
break;
case 'finished':
$this->isPrepared = true;
$this->isRunning = false;
$this->isFinished = true;
$this->hasRun = false;
break;
case 'error':
default:
$this->setError($errorMessage);
break;
}
}
/**
* The public interface to an engine part. This method takes care for
* calling the correct method in order to perform the initialisation -
* run - finalisation cycle of operation and return a proper reponse array.
*
* @param int $nesting
*
* @return array A Reponse Array
*/
final public function tick($nesting = 0)
{
$configuration = AliceFactory::getConfiguration();
$timer = AliceFactory::getTimer();
// Call the right action method, depending on engine part state
switch( $this->getState() )
{
case "init":
$this->_prepare();
$breakFlag = $configuration->set('volatile.breakflag', false);
break;
case "prepared":
$this->_run();
break;
case "running":
$this->_run();
break;
case "postrun":
$this->_finalize();
$breakFlag = $configuration->set('volatile.breakflag', false);
break;
}
// If there is still time, we are not finished and there is no break flag set, re-run the tick()
// method.
$breakFlag = $configuration->get('volatile.breakflag', false);
if(
!in_array( $this->getState(), array('finished','error') ) &&
( $timer->getTimeLeft() > 0 ) &&
!$breakFlag &&
($nesting < 20) &&
($this->nest_logging)
)
{
// Nesting is only applied if $this->nest_logging == true (currently only Kettenrad has this)
$nesting++;
if($this->nest_logging) AliceUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Batching successive steps (nesting level $nesting)");
$out = $this->tick($nesting);
}
else
{
// Return the output array
$out = $this->_makeReturnTable();
// Things to do for nest-logged parts (currently, only Kettenrad is)
if($this->nest_logging) {
if($breakFlag) AliceUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Engine steps batching: Break flag detected.");
// Reset the break flag
$configuration->set('volatile.breakflag', false);
// Log that we're breaking the step
AliceUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Batching of engine steps finished. I will now return control to the caller.");
// Enforce minimum execution time
$timer = AliceFactory::getTimer();
$timer->enforce_min_exec_time(true);
}
}
// Send a Return Table back to the caller
return $out;
}
/**
* Returns a copy of the class's status array
* @return array
*/
public function getStatusArray()
{
return $this->_makeReturnTable();
}
/**
* Sends any kind of setup information to the engine part. Using this,
* we avoid passing parameters to the constructor of the class. These
* parameters should be passed as an indexed array and should be taken
* into account during the preparation process only. This function will
* set the error flag if it's called after the engine part is prepared.
*
* @param array $parametersArray The parameters to be passed to the
* engine part.
*/
final public function setup( $parametersArray )
{
if( $this->isPrepared )
{
$this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
}
else
{
$this->_parametersArray = $parametersArray;
if(array_key_exists('root', $parametersArray))
{
$this->databaseRoot = $parametersArray['root'];
}
}
}
/**
* Returns the state of this engine part.
*
* @return string The state of this engine part. It can be one of
* error, init, prepared, running, postrun, finished.
*/
final public function getState()
{
if( $this->getError() )
{
return "error";
}
if( !($this->isPrepared) )
{
return "init";
}
if( !($this->isFinished) && !($this->isRunning) && !( $this->hasRun ) && ($this->isPrepared) )
{
return "prepared";
}
if ( !($this->isFinished) && $this->isRunning && !( $this->hasRun ) )
{
return "running";
}
if ( !($this->isFinished) && !($this->isRunning) && $this->hasRun )
{
return "postrun";
}
if ( $this->isFinished )
{
return "finished";
}
}
/**
* Constructs a Response Array based on the engine part's state.
* @return array The Response Array for the current state
*/
final protected function _makeReturnTable()
{
// Get a list of warnings
$warnings = $this->getWarnings();
// Report only new warnings if there is no warnings queue size
if( $this->_warnings_queue_size == 0 )
{
if( ($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)) ) )
{
$warnings = array_slice($warnings, $this->warnings_pointer + 1);
$this->warnings_pointer += count($warnings);
}
else
{
$this->warnings_pointer = count($warnings);
}
}
$out = array(
'HasRun' => (!($this->isFinished)),
'Domain' => $this->active_domain,
'Step' => $this->active_step,
'Substep' => $this->active_substep,
'Error' => $this->getError(),
'Warnings' => $warnings
);
return $out;
}
final protected function setDomain($new_domain)
{
$this->active_domain = $new_domain;
}
final public function getDomain()
{
return $this->active_domain;
}
final protected function setStep($new_step)
{
$this->active_step = $new_step;
}
final public function getStep()
{
return $this->active_step;
}
final protected function setSubstep($new_substep)
{
$this->active_substep = $new_substep;
}
final public function getSubstep()
{
return $this->active_substep;
}
/**
* Implement this if your Engine Part can return the percentage of its work already complete
* @return float A number from 0 (nothing done) to 1 (all done)
*/
public function getProgress()
{
return 0;
}
}