<?php

/**
* This trait helps to build wraper classes for popular template engines to make all them to have one interface
* The trait implements some functions that are commn for all templating engines wrappers
*
* LICENSE: MIT
*
* @category   Templating
* @package    Gelembjuk/Templating
* @copyright  Copyright (c) 2015 Roman Gelembjuk. (http://gelembjuk.com)
* @version    1.0
* @link       https://github.com/Gelembjuk/templating
*/

namespace Gelembjuk\Templating;

trait TemplatingTrait {
	/**
	* Template file name to process. This is a file name without extension. Can be relative path
	*
	* @var string
	*/
	protected $template;
	/**
	* Template as a string instead if a file
	*
	* @var string
	*/
	protected $template_data;
	/**
	* Templates directory. Path to a directory
	*
	* @var string
	*/
	protected $template_dir_orig;

	/**
	* Templates compilation directory. Path to a directory
	*
	* @var string
	*/
	protected $compile_dir_orig;
	/**
	 * Template variables
	 *
	 * @var array
	 */
	protected $template_vars = array();
	/**
	 * Reference to an application object that uses a templating class
	 *
	 * @var array
	 */
	protected $application = null;
	/**
	* Extension of template files. Defauls is .tpl
	*
	* @var string
	*/
	protected $templates_extension;

	/**
	 * Init a template engine. Accepts array of options
	 *
	 * templatepath - path to smarty templates directory
	 * compiledir	- path to templates compilation directory
	 * usecache	- (true|false) Torns cache On/Off .
	 * cachedir	- If cache is On then directory for cache files
	 * extension	- Extension of template files. Default is tpl
	 * templatingpluginsdir - Path (or array of paths) to folders where Smarty extra plugins are stored
	 *
	 * @param array $options
	 */
	public function init($options) {

		$this->template = '';
		$this->template_data = '';

		$this->template_dir_orig = '';

		if ($options['templatepath'] != '') {
			$this->template_dir_orig = $options['templatepath'];
		}

		// compile dir is required and must be writable
		$this->compile_dir_orig  = $options['compiledir'];

		$this->caching = $options['usecache'];

		if ($this->caching) {
			$this->cache_dir    = $options['cachedir'];
		}

		$this->templates_extension = 'tpl';

		if ($options['extension'] != '') {
			$this->templates_extension = $options['extension'];
		}

		$this->initPlugins($options['templatingpluginsdir']);

		$this->template_vars = array();
	}
	/**
	 * Set template file name to fetch later. It is only file name without extension.
	 * File must be in the configured templates folder
	 *
	 * @paral string $filepath Tempate file
	 */
	public function setTemplate($filepath) {
		$this->template = $filepath;
		$this->template_data = '';

		// if we want to use file system templtes then time to check
		$this->checkFileSystemTemplatingConfigured();
	}
	/**
	 * Set template as string. It is HTML document to fetch later as a template
	 *
	 * @paral string $html Tempate document as a string
	 */
	public function setTemplateData($html) {
		$this->template = '';
		$this->template_data = $html;

		// if we want to use file system templtes then time to check if all is set
		$this->checkStringTemplatingConfigured();
	}
	/**
	 * Set template as string. It is HTML document to fetch later as a template
	 *
	 * @paral string $filepath Template file name. Same rules as for setTemplate
	 * @param array $options Array of options. Now only one option can eb used `extensionpresent` = true means template fiel path includes extension
	 *
	 * @return boolean True if template file exists in a templates path
	 */
	public function checkTemplateExists($filepath,$options = array(),$tmplpath = '') {

		if (is_array($this->template_dir_orig) && $tmplpath == '') {
			foreach ($this->template_dir_orig as $path) {
				$exists = $this->checkTemplateExists($filepath,$options,$path);

				if ($exists) {
					return true;
				}
			}

			return false;
		}

		$filepath = (($tmplpath != '')?$tmplpath:$this->template_dir_orig).$filepath;

		if (!$options['extensionpresent']) {
			$filepath .= '.'.$this->templates_extension;
		}

		return file_exists($filepath);
	}
	/**
	 * Fetch template from given string and return complete html document
	 *
	 * @param string $htmlstring Template to fetch
	 *
	 * @return string HTML document with all data included
	 */
	public function displayPage() {
		$html = $this->fetchTemplate();
		echo $html;
		return true;
	}
	/**
	 * Fetch prepared template and dislay as html page
	 */
	public function fetchString($htmlstring) {
		$this->setTemplateData($htmlstring);
		return $this->fetchTemplate();
	}

	/**
	 * Fetch prepared template and return complete html document
	 *
	 * @return string HTML document with all data included
	 */
	public function fetchTemplate() {
		if ($this->template != '' &&
			$this->template_data == '' &&
			!$this->checkTemplateExists($this->template)) {

			throw new \Exception(sprintf('Template file %s not found',$this->template));
		}

		if ($this->template != '') {
			return $this->fetchFromFile();
		} else {
			return $this->fetchFromString();
		}
	}
	/**
	 * Check if there are all settings ready to do oprtation with templates saved in filesystem
	 * This is standard set of checks. We expect many engines can use only this
	 *
	 * @return boolean True on success and raises exception if there are problems
	 */
	protected function checkFileSystemTemplatingConfigured_PathCompileCache() {
		if (is_array($this->template_dir_orig)) {
			$found = false;

			foreach ($this->template_dir_orig as $path) {
				if ($path != '' && is_dir($path)) {
					$found = true;
					break;
				}
			}
			if (!$found) {
				throw new \Exception('Templates directory is not set');
			}
		} else {
			if (trim($this->template_dir_orig) == '') {
				throw new \Exception('Templates directory is not set');
			}
			if (!is_dir($this->template_dir_orig)) {
				throw new \Exception(sprintf('Templates directory %s not found',$this->template_dir_orig));
			}
		}
		if (trim($this->compile_dir_orig) == '') {
			throw new \Exception('Templates compile directory is not set');
		}
		if (!is_dir($this->compile_dir_orig)) {
			throw new \Exception(sprintf('Templates compile directory %s not found',$this->compile_dir_orig));
		}
		if (!is_writable($this->compile_dir_orig)) {
			throw new \Exception(sprintf('Templates compile directory %s is not writable',$this->compile_dir_orig));
		}
		if ($this->caching) {
			// if cache is On the cache path must be present
			if (!is_dir($this->cache_dir)) {
				throw new \Exception(sprintf('Templates cache directory %s not found',$this->cache_dir));
			}
			if (!is_writable($this->cache_dir)) {
				throw new \Exception(sprintf('Templates cache directory %s is not writable',$this->cache_dir));
			}
		}
		return true;
	}
	/**
	 * Check if there are all settings ready to do oprtation with templates saved in filesystem
	 *
	 * @return boolean True on success and raises exception if there are problems
	 */
	protected function checkFileSystemTemplatingConfigured() {
		return $this->checkFileSystemTemplatingConfigured_PathCompileCache();
	}
	/**
	 * Check if there are all settings ready to do oprtation with templates presented as a string
	 *
	 * @return boolean True on success and raises exception if there are problems
	 */
	protected function checkStringTemplatingConfigured() {
		return true;
	}
	/**
	 * Init plugins and extra plugins on the engine
	 *
	 * @param string|array $plugins Path(s) to plugins sirectory
	 */
	protected function initPlugins($plugins = '') {
		return true;
	}
	/**
	 * Returns application object reference
	 *
	 * @return object Application object
	 */
	public function getApplication() {
		return $this->application;
	}
	/**
	 * Sets application object referehce. Application can be used in plugins
	 *
	 * @param object $application Application object
	 */
	public function setApplication($application) {
		$this->application = $application;
	}
	/**
	 * Returns a path to a directory with plugins. Can be used for cases
	 * when plugins should be used with an external application
	 *
	 * @return string Path to a directory with native plugins
	 */
	public function getNativePluginsDir() {
		return '';
	}
	/**
	 * Fetch prepared file template and return complete html document
	 *
	 * @return string HTML document with all data included
	 */
	abstract protected function fetchFromFile();

	/**
	 * Fetch prepared string template and return complete html document
	 *
	 * @return string HTML document with all data included
	 */
	abstract protected function fetchFromString();
}


<?php

/*
 * This file is part of the frenzy-framework package.
 *
 * (c) Gustavo Falco <comfortablynumb84@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace IronEdge\Component\Config;

use IronEdge\Component\CommonUtils\Data\DataInterface;
use IronEdge\Component\CommonUtils\Data\DataTrait;
use IronEdge\Component\CommonUtils\Options\OptionsInterface;
use IronEdge\Component\Config\Exception\ImportException;
use IronEdge\Component\Config\Exception\InvalidOptionTypeException;
use IronEdge\Component\Config\Reader\ArrayReader;
use IronEdge\Component\Config\Reader\FileReader;
use IronEdge\Component\Config\Reader\ReaderInterface;
use IronEdge\Component\Config\Writer\WriterInterface;
use IronEdge\Component\Config\Writer\ArrayWriter;
use IronEdge\Component\Config\Writer\FileWriter;

/*
 * @author Gustavo Falco <comfortablynumb84@gmail.com>
 */
class Config implements ConfigInterface, OptionsInterface
{
    use DataTrait {
        setOptions as traitSetOptions;
    }

    /**
     * Reader instance.
     *
     * @var ReaderInterface
     */
    private $_reader;

    /**
     * Writer instance.
     *
     * @var WriterInterface
     */
    private $_writer;

    /**
     * Constructor.
     *
     * @param array $data    - Data.
     * @param array $options - Options.
     */
    public function __construct(array $data = [], array $options = [])
    {
        $this->setOptions($options)
            ->setData($data);
    }

    /**
     * Loads the configuration using the reader instance.
     *
     * @param array $options - Options.
     *
     * @throws InvalidOptionTypeException
     * @throws ImportException
     *
     * @return $this
     */
    public function load(array $options = [])
    {
        $options = array_replace_recursive(
            $this->getOption('loadOptions', []),
            $options
        );

        // Simple shortcuts
        $options['readerOptions']['file'] = $options['readerOptions']['file'] ?? $options['file'];
        $options['readerOptions']['data'] = $options['readerOptions']['data'] ?? $options['data'];

        $reader = $this->getReader();
        $data = $reader->read($options['readerOptions']);

        if ($options['processImports'] && isset($data['import'])) {
            if (!is_array($data['import'])) {
                throw ImportException::create('"import" parameter must be an array.');
            }

            foreach ($data['import'] as $importData) {
                if (!is_array($importData)) {
                    throw ImportException::create('Each "import" array element must be an array.');
                }

                if (isset($importData['file'])) {
                    if ($options['file']) {
                        $importData['file'] = $importData['file']{0} === '/' ?
                            $importData['file'] :
                            dirname($options['file']).'/'.$importData['file'];
                    }

                    if (!is_file($importData['file'])) {
                        continue;
                    }
                }

                $readOptions = array_replace_recursive($options['readerOptions'], $importData);
                $data = array_replace_recursive($data, $reader->read($readOptions));
            }
        }

        if ($options['clearFirst']) {
            $this->setData([], false);
        }

        if ($options['loadInKey'] !== null) {
            if (!is_string($options['loadInKey'])) {
                throw InvalidOptionTypeException::create('loadInKey', 'string');
            }

            $this->set(
                $options['loadInKey'],
                $this->replaceTemplateVariables(array_replace_recursive($this->get($options['loadInKey'], []), $data))
            );
        } else {
            $this->setData(array_replace_recursive($this->getData(), $data));
        }

        /** @var Callable $callable */
        $callable = $this->getOption('onAfterLoad');

        if (!is_callable($callable)) {
            throw new \RuntimeException('Option "onAfterLoad" must be a callable.');
        }

        call_user_func_array($callable, [$this, $options]);

        return $this;
    }

    /**
     * Saves the configuration with the writer instance.
     *
     * @param array $options - Options.
     *
     * @return $this
     */
    public function save(array $options = [])
    {
        $options = array_merge(
            [
                'file'              => null,
                'writerOptions'     => []
            ],
            $options
        );

        // Simple shortcut
        $options['writerOptions']['file'] = $options['file'];

        /** @var Callable $callable */
        $callable = $this->getOption('onBeforeSave');

        if (!is_callable($callable)) {
            throw new \RuntimeException('Option "onBeforeSave" must be a callable.');
        }

        call_user_func_array($callable, [$this, $options]);

        $this->getWriter()->write($this->getData(), $options['writerOptions']);

        return $this;
    }

    /**
     * Returns the reader instance used by this object.
     *
     * @return ReaderInterface
     */
    public function getReader()
    {
        return $this->_reader;
    }

    /**
     * Sets the reader instance to use with this config object.
     *
     * @param ReaderInterface $reader - reader.
     *
     * @return $this
     */
    public function setReader($reader)
    {
        $this->_reader = $reader;

        return $this;
    }

    /**
     * Returns the writer instance used by this object.
     *
     * @return WriterInterface
     */
    public function getWriter()
    {
        return $this->_writer;
    }

    /**
     * Sets the writer instance to use with this config object.
     *
     * @param WriterInterface $writer - writer.
     *
     * @return $this
     */
    public function setWriter($writer)
    {
        $this->_writer = $writer;

        return $this;
    }

    /**
     * Executes initialization tasks.
     *
     * @return void
     */
    protected function initialize()
    {
        $this->initializeReader();
        $this->initializeWriter();
    }

    /**
     * Initializes the reader instance.
     *
     * @return void
     */
    protected function initializeReader()
    {
        $reader = $this->getOption('reader');

        if (is_string($reader)) {
            switch ($reader) {
                case 'array':
                    $reader = new ArrayReader();

                    break;
                case 'file':
                    $reader = new FileReader();

                    break;
                default:
                    throw new \InvalidArgumentException(
                        'Invalid reader "'.$reader.'". Valid reader strings: array, file.'
                    );
            }
        } else if (!is_object($reader) || !($reader instanceof ReaderInterface)) {
            throw new \InvalidArgumentException(
                'Option "reader" must be a string or an instance of IronEdge\Component\Config\Reader\ReaderInterface.'
            );
        }

        $this->setReader($reader);
    }

    /**
     * Initializes the writer instance.
     *
     * @return void
     */
    protected function initializeWriter()
    {
        $writer = $this->getOption('writer');

        if (is_string($writer)) {
            switch ($writer) {
                case 'array':
                    $writer = new ArrayWriter();

                    break;
                case 'file':
                    $writer = new FileWriter();

                    break;
                default:
                    throw new \InvalidArgumentException(
                        'Invalid writer "'.$writer.'". Valid writer strings: array, file.'
                    );
            }
        } else if (!is_object($writer) || !($writer instanceof WriterInterface)) {
            throw new \InvalidArgumentException(
                'Option "writer" must be a string or an instance of IronEdge\Component\Config\Writer\WriterInterface.'
            );
        }

        $this->setWriter($writer);
    }

    /**
     * Sets options.
     *
     * @param array $options - Options.
     *
     * @return $this
     */
    public function setOptions(array $options)
    {
        $ret = $this->traitSetOptions($options);

        $this->initialize();

        return $ret;
    }

    /**
     * Returns the default options.
     *
     * @return array
     */
    public function getDefaultOptions(): array
    {
        return [
            'reader'                => 'file',
            'writer'                => 'file',
            'onAfterLoad'           => function(Config $config, array $options) {},
            'onBeforeSave'          => function(Config $config, array $options) {},
            'separator'             => '.',
            'templateVariables'     => [],
            'loadOptions'           => [
                'data'              => null,
                'file'              => null,
                'loadInKey'         => null,
                'processImports'    => false,
                'clearFirst'        => false,
                'readerOptions'     => []
            ]
        ];
    }

}


<?php
/**
 * Абстрактная таблица БД
 *
 * @version ${product.version}
 *
 * @copyright 2011, Михаил Красильников <m.krasilnikov@yandex.ru>
 * @license http://www.gnu.org/licenses/gpl.txt	GPL License 3
 * @author Михаил Красильников <m.krasilnikov@yandex.ru>
 *
 * Данная программа является свободным программным обеспечением. Вы
 * вправе распространять ее и/или модифицировать в соответствии с
 * условиями версии 3 либо (по вашему выбору) с условиями более поздней
 * версии Стандартной Общественной Лицензии GNU, опубликованной Free
 * Software Foundation.
 *
 * Мы распространяем эту программу в надежде на то, что она будет вам
 * полезной, однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ, в том
 * числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ и ПРИГОДНОСТИ ДЛЯ
 * ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ. Для получения более подробной
 * информации ознакомьтесь со Стандартной Общественной Лицензией GNU.
 *
 * Вы должны были получить копию Стандартной Общественной Лицензии
 * GNU с этой программой. Если Вы ее не получили, смотрите документ на
 * <http://www.gnu.org/licenses/>
 *
 * @package ORM
 */

/**
 * Абстрактная таблица БД
 *
 * @package ORM
 * @since 1.00
 */
abstract class ORM_Table
{
    /**
     * Модуль
     *
     * @var Eresus_Plugin
     */
    private $plugin;

    /**
     * Драйвер СУБД
     * @var ORM_Driver_SQL
     * @since 2.01
     */
    private $driver;

    /**
     * Имя таблицы
     *
     * @var string
     */
    private $tableName;

    /**
     * Признак того, что таблица является псевдонимом другой
     *
     * В этом случае в {@link tableName} хранится имя основной таблицы
     *
     * @var bool
     * @since 3.00
     */
    private $isAlias = false;

    /**
     * Описание столбцов
     *
     * @var array
     */
    private $columns = array();

    /**
     * Имя класса сущности
     *
     * @var string
     */
    private $entityClass = null;

    /**
     * Имя поля основного ключа
     *
     * @var string
     */
    private $primaryKey = 'id';

    /**
     * Порядок сортировки
     *
     * Массив пар «поле=>ezcQuerySelect::ASC/DESC»
     *
     * @var array
     */
    private $ordering = array();

    /**
     * Индексы
     *
     * @var array
     */
    private $indexes = array();

    /**
     * Реестр объектов
     *
     * @var ORM_Entity[]
     *
     * @since 3.00
     */
    private $registry = array();

    /**
     * Конструктор
     *
     * @param Eresus_Plugin  $plugin
     * @param ORM_Driver_SQL $driver
     *
     * @return ORM_Table
     *
     * @since 1.00
     */
    public function __construct($plugin, ORM_Driver_SQL $driver)
    {
        $this->plugin = $plugin;
        $this->driver = $driver;
        $this->setTableDefinition();
    }

    /**
     * Возвращает модуль-владелец
     *
     * @return Eresus_Plugin
     *
     * @since 3.00
     */
    public function getPlugin()
    {
        return $this->plugin;
    }

    /**
     * Возвращает драйвер СУБД
     *
     * @return ORM_Driver_SQL
     *
     * @since 3.00
     */
    public function getDriver()
    {
        return $this->driver;
    }

    /**
     * Возвращает имя таблицы
     *
     * @return string
     *
     * @since 3.00
     */
    public function getName()
    {
        return $this->tableName;
    }

    /**
     * Возвращает имя таблицы
     *
     * @return string
     *
     * @since 1.00, публичный с 3.00
     * @deprecated используйте {@link getName()}
     */
    public function getTableName()
    {
        return $this->getName();
    }

    /**
     * Возвращает имя класса сущности
     *
     * @return string
     *
     * @since 1.00, публичный с 3.00
     */
    public function getEntityClass()
    {
        if (is_null($this->entityClass))
        {
            $thisClass = get_class($this);
            $this->entityClass = str_replace('_Table_', '_', $thisClass);
        }
        return $this->entityClass;
    }

    /**
     * Возвращает поля таблицы
     *
     * @return ORM_Field_Abstract[]
     *
     * @since 3.00
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Возвращает имя основного ключа
     *
     * @return string
     *
     * @since 1.00, публичный с 3.00
     */
    public function getPrimaryKey()
    {
        return $this->primaryKey;
    }

    /**
     * Назначает основной ключ
     *
     * @param string|array $key
     *
     * @since 3.00
     */
    protected function setPrimaryKey($key)
    {
        $this->primaryKey = $key;
    }

    /**
     * Возвращает сортировку
     *
     * @return array
     *
     * @since 3.00
     */
    public function getOrdering()
    {
        return $this->ordering;
    }

    /**
     * Возвращает индексы
     *
     * @return array
     *
     * @since 3.00
     */
    public function getIndexes()
    {
        return $this->indexes;
    }

    /**
     * Возвращает true если этот класс является псевдонимом другой таблицы
     *
     * @return bool
     *
     * @since 3.00
     */
    public function isAlias()
    {
        return $this->isAlias;
    }

    /**
     * Помещает сущность в таблицу
     *
     * @param ORM_Entity $entity
     *
     * @return void
     *
     * @since 1.00
     */
    public function persist(ORM_Entity $entity)
    {
        $q = Eresus_DB::getHandler()->createInsertQuery();
        $q->insertInto($this->getName());
        $this->bindValuesToQuery($entity, $q);
        $newQuery = $entity->beforeSave($q);
        if ($newQuery instanceof ezcQuery)
        {
            $q = $newQuery;
        }
        $q->execute();
        $entity->afterSave();
    }

    /**
     * Обновляет сущность в таблице
     *
     * @param ORM_Entity $entity
     *
     * @return void
     *
     * @since 1.00
     */
    public function update(ORM_Entity $entity)
    {
        $pKey = $this->getPrimaryKey();
        $columns = $this->getColumns();
        $q = Eresus_DB::getHandler()->createUpdateQuery();
        $q->update($this->getName())->
            where($q->expr->eq($pKey,
                $q->bindValue($entity->$pKey, null, $columns[$pKey]->getPdoType())
            ));
        $this->bindValuesToQuery($entity, $q);
        $newQuery = $entity->beforeSave($q);
        if ($newQuery instanceof ezcQuery)
        {
            $q = $newQuery;
        }
        $q->execute();
        $entity->afterSave();
    }

    /**
     * Удаляет сущность из таблицы
     *
     * @param ORM_Entity $entity
     *
     * @return void
     *
     * @since 1.00
     */
    public function delete(ORM_Entity $entity)
    {
        $pKey = $this->getPrimaryKey();
        $columns = $this->getColumns();
        $q = Eresus_DB::getHandler()->createDeleteQuery();
        $q->deleteFrom($this->getName())->
            where($q->expr->eq($pKey,
                $q->bindValue($entity->getPrimaryKey(), null, $columns[$pKey]->getPdoType())
            ));
        $entity->beforeDelete($q);
        $q->execute();
        $entity->afterDelete();
    }

    /**
     * Возвращает количество записей
     *
     * @param ezcQuerySelect $query
     *
     * @return int
     *
     * @since 1.00
     */
    public function count(ezcQuerySelect $query = null)
    {
        $q = $query ? $query : $this->createCountQuery();
        $raw = $q->fetch();
        if ($raw)
        {
            return $raw['record_count'];
        }
        return 0;
    }

    /**
     * Возвращает все записи
     *
     * @param int $limit   максимум элементов, который следует вернуть
     * @param int $offset  сколько элементов пропустить
     *
     * @return array
     *
     * @since 1.00
     */
    public function findAll($limit = null, $offset = 0)
    {
        $q = $this->createSelectQuery();
        $items = $this->loadFromQuery($q, $limit, $offset);
        return $items;
    }

    /**
     * Возвращает все записи, удовлетворяющие фильтру
     *
     * @param array $filter  набор пар «поле => значение»
     *
     * @throws LogicException
     *
     * @return ORM_Entity[]
     *
     * @since 3.00
     */
    public function findAllBy(array $filter)
    {
        $q = $this->createSelectQuery();
        $where = array();
        $columns = $this->getColumns();
        foreach ($filter as $field => $value)
        {
            if (!array_key_exists($field, $columns))
            {
                throw new LogicException(sprintf('Unknown column "%s" in filter', $field));
            }
            if (!$columns[$field]->canBeUsedInWhere())
            {
                throw new LogicException(
                    sprintf('Field "%s" does not supports filtering', $field));
            }
            $where []= $q->expr->eq($field, $q->bindValue(
                $columns[$field]->orm2pdo($value),
                ":$field",
                $columns[$field]->getPdoType())
            );
        }
        $q->where($q->expr->lAnd($where));
        return $this->loadFromQuery($q);
    }

    /**
     * Возвращает сущность по основному ключу
     *
     * @param mixed $id
     *
     * @return ORM_Entity
     *
     * @since 1.00
     */
    public function find($id)
    {
        if (array_key_exists($id, $this->registry))
        {
            return $this->registry[$id];
        }

        $columns = $this->getColumns();
        $pKey = $this->getPrimaryKey();
        $q = $this->createSelectQuery();
        $q->where($q->expr->eq($pKey, $q->bindValue($id, null, $columns[$pKey]->getPdoType())));
        $entity = $this->loadOneFromQuery($q);

        if (null !== $entity)
        {
            $this->registry[$id] = $entity;
        }
        return $entity;
    }

    /**
     * Возвращает заготовку запроса SELECT
     *
     * @param bool $fill  заполнить запрос начальными данными
     *
     * @return ezcQuerySelect
     *
     * @since 1.00
     */
    public function createSelectQuery($fill = true)
    {
        $q = Eresus_DB::getHandler()->createSelectQuery();
        if ($fill)
        {
            $columns = $this->getColumns();

            $select = array($this->getName() . '.*');
            foreach ($columns as $column)
            {
                $select = $column->onSelect($q, $select);
            }
            $q->selectDistinct($select);

            $q->from($this->getName());
            foreach ($columns as $column)
            {
                $column->onFrom($q);
            }
            if (count($this->getOrdering()) > 0)
            {
                foreach ($this->getOrdering() as $orderBy)
                {
                    $q->orderBy($orderBy[0], $orderBy[1]);
                }
            }
        }
        else
        {
            $q->from($this->getName());
        }
        return $q;
    }

    /**
     * Возвращает заготовку запроса SELECT для подсчёта количества записей
     *
     * @return ezcQuerySelect
     *
     * @since 1.00
     */
    public function createCountQuery()
    {
        $q = Eresus_DB::getHandler()->createSelectQuery();
        $q->select($q->alias($q->expr->count('*'), 'record_count'));
        $q->from($this->getName());
        $q->limit(1);
        return $q;
    }

    /**
     * Возвращает массив сущностей на основе запроса
     *
     * @param ezcQuerySelect $query
     * @param int            $limit   максимум элементов, который следует вернуть
     * @param int            $offset  сколько элементов пропустить
     *
     * @return ORM_Entity_Collection
     *
     * @since 1.00
     */
    public function loadFromQuery(ezcQuerySelect $query, $limit = null, $offset = 0)
    {
        if ($limit)
        {
            $query->limit($limit, $offset);
        }
        $raw = $query->fetchAll();
        $items = new ORM_Entity_Collection();
        if ($raw)
        {
            foreach ($raw as $attrs)
            {
                $items->attach($this->entityFactory($attrs));
            }
        }
        return $items;
    }

    /**
     * Возвращает сущность на основе запроса
     *
     * @param ezcQuerySelect $query
     *
     * @return ORM_Entity
     *
     * @since 1.00
     */
    public function loadOneFromQuery(ezcQuerySelect $query)
    {
        $query->limit(1);
        $attrs = $query->fetch();
        if ($attrs)
        {
            return $this->entityFactory($attrs);
        }
        return null;
    }

    /**
     * Метод должен устанавливать свойства таблицы БД
     *
     * В этом методе необходимо сделать следующее:
     *
     * 1. Задать имя таблицы вызовом {@link setTableName()}
     * 2. Определить столбцы вызовом {@link hasColumns()}
     * 3. Создать необходимые индексы вызовом {@link index()}
     *
     * @return void
     *
     * @since 1.00
     */
    abstract protected function setTableDefinition();

    /**
     * Устанавливает имя таблицы
     *
     * @param string $name
     *
     * @throws LogicException  если перед setTableName() был вызван {@link isAlias()} или
     *                         setTableName() вызван повторно
     * @return void
     *
     * @since 1.00
     */
    protected function setTableName($name)
    {
        if (null !== $this->tableName)
        {
            if ($this->isAlias)
            {
                throw new LogicException(
                    'Method setTableName() can not be executed after isAlias()');
            }
            else
            {
                throw new LogicException(
                    'Method setTableName() can not be executed more then once');
            }
        }
        $this->tableName = $name;
    }

    /**
     * Объявляет таблицу псевдонимом другой таблицы
     *
     * Если вы используете этот метод в {@link setTableDefinition()}, то методы
     * {@link setTableName()} и {@link index()} использовать нельзя.
     *
     * @param string $tableName  имя основной таблицы (это имя именно таблицы БД, а не её класса)
     *
     * @throws LogicException  если перед isAlias() был вызван {@link setTableName()} или isAlias()
     *                         вызван повторно
     *
     * @since 3.00
     */
    protected function isAliasFor($tableName)
    {
        if (null !== $this->tableName)
        {
            if ($this->isAlias)
            {
                throw new LogicException(
                    'Method isAlias() can not be executed more then once');
            }
            else
            {
                throw new LogicException(
                    'Method isAlias() can not be executed after setTableName()');
            }
        }
        $this->tableName = $tableName;
        $this->isAlias = true;
    }

    /**
     * Устанавливает описания столбцов
     *
     * Аргумент $columns должен быть ассоциативным массивом, где каждый элемент соответствует
     * одному столбцу таблицы, при этом ключ элемента должен задавать имя столбца, а тело элемента —
     * описание этого столбца. В свою очередь, тело элемента должно быть ассоциативным массивом, в
     * котором можно использовать следующие ключи:
     *
     * - type (string) — тип поля (см. ниже).
     * - autoincrement (bool) — Автоинкрементное. Допустим только у первого столбца.
     * - length (int) — размер столбца.
     * - unsigned (bool) — беззнаковое число.
     *
     * Первый столбец всегда назначается основным ключом.
     *
     * Возможные типы полей:
     *
     * - bindings — список привязок к другим сущностям
     * - boolean
     * - date
     * - datetime
     * - entity — {@link ORM_Field_Entities ссылка на другую сущность}
     * - entities — коллекция других сущностей
     * - float
     * - integer
     * - string
     * - time
     * - timestamp — хранится как целое число
     *
     * Более подробные описания этих типов можно найти в документации по соответствующим классам
     * ORM_Field_xxx.
     *
     * @param array $columns
     *
     * @throws InvalidArgumentException
     *
     * @return void
     *
     * @since 1.00
     */
    protected function hasColumns(array $columns)
    {
        $fieldTypes = $this->getDriver()->getManager()->getFieldTypes();
        $this->columns = array();
        foreach ($columns as $name => $attrs)
        {
            if (!is_string($name))
            {
                throw new InvalidArgumentException(sprintf(
                    'Column name must be a string, got "%s"', gettype($name)));
            }
            if (!preg_match('/^[a-z_]+$/i', $name))
            {
                throw new InvalidArgumentException(sprintf(
                    'Column name must be a non empty string consisted of "a-zA-Z" or "_", got "%s"',
                    $name));
            }
            if (!is_array($attrs))
            {
                throw new InvalidArgumentException(
                    sprintf('Definition of column "%s" is not an array', $name));
            }
            if (!array_key_exists('type', $attrs))
            {
                throw new InvalidArgumentException(
                    sprintf('No "type" element in "%s" definition', $name));
            }
            if (!array_key_exists($attrs['type'], $fieldTypes))
            {
                throw new InvalidArgumentException(
                    sprintf('Unsupported type "%s" in "%s" definition', $attrs['type'], $name));
            }
            $fieldTypeClass = $fieldTypes[$attrs['type']];
            unset($attrs['type']);
            $this->columns[$name] = new $fieldTypeClass($this, $name, $attrs);
        }
        reset($columns);
        $this->primaryKey = key($columns);
    }

    /**
     * Определяет индекс
     *
     * @param string $name    имя индекса
     * @param array  $params  свойства индекса
     *
     * @return void
     *
     * @since 1.00
     */
    protected function index($name, array $params)
    {
        $this->indexes[$name] = $params;
    }

    /**
     * Задаёт порядок сортировки по умолчанию
     *
     * Если порядок сортировки не задан, но есть поле «position» сортировка будет осуществляться по
     * нему по умолчанию.
     *
     * Примеры:
     * <code>
     * // По порядковому номеру
     * $this->setOrdering('position');
     * // По дате, новые в начале
     * $this->setOrdering('date', ezcQuerySelect::DESC);
     * // Сначала по названию, потом по дате
     * $this->setOrdering('title', 'ASC', 'date', 'DESC');
     * </code>
     *
     * @return void
     *
     * @since 1.00
     */
    protected function setOrdering()
    {
        $args = func_get_args();
        if (count($args) % 2 != 0)
        {
            $args []= ezcQuerySelect::ASC;
        }
        $this->ordering = array();
        while (count($args))
        {
            $field = array_shift($args);
            $dir = array_shift($args);
            $this->ordering []= array($field, $dir);
        }
    }

    /**
     * Фабрика сущностей
     *
     * @param array $values
     *
     * @throws InvalidArgumentException
     *
     * @return ORM_Entity
     *
     * @since 1.00
     */
    protected function entityFactory(array $values)
    {
        if (!array_key_exists($this->getPrimaryKey(), $values))
        {
            throw new InvalidArgumentException('Primary key not found in a given array');
        }

        $id = $values[$this->getPrimaryKey()];
        if (array_key_exists($id, $this->registry))
        {
            return $this->registry[$id];
        }

        $entityClass = $this->getEntityClass();
        $entity = new $entityClass($values);
        $this->registry[$id] = $entity;
        return $entity;
    }

    /**
     * Привязывает значения свойств объекта к запросу
     *
     * @param ORM_Entity $entity  объект
     * @param ezcQuery   $query   запрос
     */
    protected function bindValuesToQuery(ORM_Entity $entity, ezcQuery $query)
    {
        /** @var ezcQueryInsert|ezcQueryUpdate $query */
        foreach ($this->getColumns() as $name => $column)
        {
            if ($column->isVirtual())
            {
                continue;
            }
            $value = $entity->getPdoValue($name);
            $query->set($name, $query->bindValue($value, ":$name", $column->getPdoType()));
        }
    }
}


<?php
/**
 * This source file is part of Xloit project.
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the MIT License that is bundled with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * <http://www.opensource.org/licenses/mit-license.php>
 * If you did not receive a copy of the license and are unable to obtain it through the world-wide-web,
 * please send an email to <license@xloit.com> so we can send you a copy immediately.
 *
 * @license   MIT
 * @link      http://xloit.com
 * @copyright Copyright (c) 2016, Xloit. All rights reserved.
 */

namespace Xloit\Bridge\Zend\Session\SaveHandler\Database;

/**
 * A {@link CryptDatabase} class.
 *
 * @package Xloit\Bridge\Zend\Session\SaveHandler\Database
 */
class CryptDatabase extends Database
{
    /**
     *
     *
     * @var string
     */
    const CRYPT_MODE = 'AES-128-CBC';

    /**
     *
     *
     * @param string $id
     * @param string $data
     *
     * @return string
     */
    protected function sanitizeDataRead($id, $data)
    {
        return (string) $this->decrypt($data, $id);
    }

    /**
     *
     *
     * @param string $id
     * @param string $data
     *
     * @return string
     */
    protected function sanitizeDataWrite($id, $data)
    {
        return $this->encrypt((string) $data, $id);
    }

    /**
     * Encrypt the given data.
     *
     * @param string $data Session data to encrypt.
     * @param string $key
     *
     * @return string Encrypted data.
     */
    protected function encrypt($data, $key)
    {
        $keySize = openssl_cipher_iv_length(static::CRYPT_MODE);
        $padding = $keySize - (mb_strlen($data, '8bit') % $keySize);
        /** @noinspection CryptographicallySecureRandomnessInspection */
        $iv   = openssl_random_pseudo_bytes($keySize);
        $key  = $this->generateKey($key);
        $data .= str_repeat(chr($padding), $padding);

        // add in our IV and base64 encode the data
        $data = base64_encode(
            $iv
            . openssl_encrypt(
                $data, static::CRYPT_MODE, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv
            )
        );

        return $data;
    }

    /**
     * Decrypt the given session data.
     *
     * @param string $data Data to decrypt
     * @param string $key
     *
     * @return string Decrypted data
     */
    protected function decrypt($data, $key)
    {
        $base64 = base64_decode($data, true);

        $keySize = openssl_cipher_iv_length(static::CRYPT_MODE);
        $key     = $this->generateKey($key);

        $iv      = substr($base64, 0, $keySize);
        $results = substr($base64, $keySize);

        $results = openssl_decrypt(
            $results, static::CRYPT_MODE, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv
        );

        return $results;
    }

    /**
     * Generate secret key.
     *
     * @param string $key
     *
     * @return string Generated secret key
     */
    protected function generateKey($key)
    {
        $keySize = openssl_cipher_iv_length(static::CRYPT_MODE);
        $key     = hash(
            'SHA256', $this->options->getClassName() . ':' . $this->sessionName . ':' . $key, true
        );

        return substr($key, 0, $keySize);
    }
}


<?php

namespace Reaction\Base;

use Reaction\StaticApplicationInterface;
use Reaction\Exceptions\HttpExceptionInterface;
use Reaction\Base\Logger\StdioLogger;
use Reaction\Web\Response;
use Reaction\Helpers\ArrayHelper;

/**
 * Class ExceptionHandler
 * @package app\base
 */
class ExceptionHandler extends BaseObject
{
    public $envType = StaticApplicationInterface::APP_ENV_PROD;

    /**
     * Throw error description to user (browser)
     * @param \Throwable $exception
     * @param bool       $asJson
     * @return Response
     */
    public function throwToUser($exception, $asJson = false)
    {
        $httpCode = method_exists($exception, 'getHttpCode') ? call_user_func([$exception, 'getHttpCode']) : 500;
        $full = $this->envType !== StaticApplicationInterface::APP_ENV_PROD;
        if ($asJson) {
            $exceptionData = $this->getExceptionData($exception, $full, false);
        } else {
            $exceptionData = $this->renderException($exception, $full);
        }
        return $this->getResponse($httpCode, [], $exceptionData);
    }

    /**
     * Throw error description to Stdout
     * @param \Throwable $exception
     * @param bool       $full
     * @param bool       $asJson
     * @return Response
     */
    public function throwToStdout($exception, $full = true, $asJson = false)
    {
        $httpCode = method_exists($exception, 'getHttpCode') ? call_user_func([$exception, 'getHttpCode']) : 500;
        $logger = $this->getStdioLogger();
        $message = $this->getExceptionData($exception, $full, true);
        if ($logger) {
            $logger->error($message, [], 1);
        } else {
            echo $message;
        }
        if ($asJson) {
            $exceptionData = $this->getExceptionData($exception, $full, false);
        } else {
            $exceptionData = 'Error: ' . $exception->getMessage();
        }
        return $this->getResponse($httpCode, [], $exceptionData);
    }

    /**
     * Render exception view
     * @param \Throwable $exception
     * @param bool       $full
     * @return array|string
     */
    private function renderException($exception, $full = false)
    {
        $app = \Reaction::$app;
        try {
            $rendered = View::renderPhpStateless($this->getViewFileForException($exception), [
                'exceptionName' => $this->getExceptionName($exception),
                'exception' => $exception,
                'app' => $app,
                'rootPathLength' => strlen($app->getAlias('@root'))
            ]);
        } catch (\Throwable $e) {
            $rendered = $this->getExceptionData($exception, $full, true);
        }
        return $rendered;
    }

    /**
     * Get view file for exception depending on its code|httpStatusCode
     * @param \Throwable $exception
     * @return string
     */
    private function getViewFileForException($exception)
    {
        $basePath = \Reaction::$app->getAlias('@views/error');
        $tplName = 'general.php';
        if ($exception instanceof HttpExceptionInterface) {
            $code = $exception->statusCode;
        } else {
            $code = $exception->getCode();
        }
        if (file_exists($basePath . '/' . $code . '.php')) {
            $tplName = $code . '.php';
        }
        return $basePath . '/' . $tplName;
    }

    /**
     * Get error response
     * @param int    $code
     * @param string $body
     * @param array  $headers
     * @return Response
     */
    private function getResponse($code = 500, $headers = [], $body = null)
    {
        if (isset($body) && is_array($body)) {
            if (!isset($body['error'])) {
                $body = ['error' => $body];
            }
            $body = json_encode($body);
            $headers['Content-type'] = 'application/json';
        }
        return new Response($code, $headers, $body);
    }

    /**
     * Get exception data as string or array
     * @param \Throwable $exception
     * @param bool       $full
     * @param bool       $plainText
     * @return array|string
     */
    private function getExceptionData($exception, $full = false, $plainText = false)
    {
        if ($plainText) {
            $text = $exception->getMessage() . "\n";
            if ($full) {
                $text .= $exception->getFile() . " #" . $exception->getLine() . "\n";
                $text .= $exception->getTraceAsString() . "\n";
            }
            return $text;
        } else {
            $data = [
                'name' => $this->getExceptionName($exception),
                'message' => $exception->getMessage(),
                'code' => $exception->getCode(),
            ];
            if ($full) {
                $data = ArrayHelper::merge($data, [
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                    'stacktrace' => $exception->getTrace(),
                ]);
            }
            return $data;
        }
    }

    /**
     * Get exception name (title)
     * @param \Throwable $exception
     * @return mixed
     */
    private function getExceptionName($exception)
    {
        return method_exists($exception, 'getName') ? call_user_func([$exception, 'getName']) : end(explode('\\', get_class($exception)));
    }

    /**
     * Get StdioLogger instance
     * @return StdioLogger|null
     */
    private function getStdioLogger()
    {
        try {
            /** @var StdioLogger $logger */
            $logger = \Reaction::$di->get('stdioLogger');
        } catch (\Throwable $exception) {
            $logger = null;
        }
        return $logger;
    }
}


<?php

namespace Reaction\Web\Sessions;

use Reaction\Promise\ExtendedPromiseInterface;
use Reaction\Cache\ExtendedCacheInterface;
use Reaction\Exceptions\SessionException;
use Reaction\Promise\Promise;
use function Reaction\Promise\all;
use function Reaction\Promise\reject;

/**
 * Class CachedSessionHandler
 * @package Reaction\Web\Sessions
 */
class CachedSessionHandler extends SessionHandlerAbstract
{
    /** @var string|ExtendedCacheInterface */
    public $cache = 'arrayCacheDefault';
    /** @var string Key prefix for storage */
    public $keyPrefix = 'sess_';

    /** @var string Array key where user data is located */
    public $dataKey = '_data';
    /** @var string Array key where timestamp is located */
    public $timestampKey = '_ts';
    /** @var array Session keys with timestamp */
    protected $keys = [];

    /**
     * @inheritdoc
     */
    public function init()
    {
        if (is_string($this->cache) || is_array($this->cache)) {
            $this->cache = \Reaction::create($this->cache);
        } elseif (!$this->cache instanceof ExtendedCacheInterface) {
            $message = sprintf("Property `cache` must be set with instance of `%s` or configuration array|string", ExtendedCacheInterface::class);
            throw new SessionException($message);
        }
        parent::init();
    }

    /**
     * Read session data and returns serialized|encoded data
     * @param string $id
     * @return ExtendedPromiseInterface  with session data
     */
    public function read($id)
    {
        $key = $this->getSessionKey($id);
        return $this->getDataFromCache($key)->then(
            function($record) {
                return $this->extractData($record, true);
            }
        )->then(
            null,
            function($error = null) use ($id) {
                //return $self->restoreSessionData($id, true)->then(
                return $this->archive->get($id)->then(
                    function($data) use ($id, $error) {
                        if (is_array($data)) {
                            return $this->write($id, $data);
                        } else {
                            return \Reaction\Promise\reject($error);
                        }
                    }
                );
            }
        )->then(
            null,
            function($error = null) use ($id) {
                $message = sprintf('Failed to read session data for "%s"', $id);
                throw new SessionException($message, 0, $error);
            }
        );
    }

    /**
     * Write session data to storage
     * @param string $id
     * @param array  $data
     * @return ExtendedPromiseInterface  with session data
     */
    public function write($id, $data)
    {
        $key = $this->getSessionKey($id);
        $record = $this->packRecord($data);
        return $this->writeDataToCache($key, $record)->then(
            function() use ($id) {
                $this->keys[$id] = time();
                return $this->read($id);
            },
            function($error = null) use ($id) {
                $message = sprintf('Failed to write session data for "%s"', $id);
                throw new SessionException($message, 0, $error);
            }
        );
    }

    /**
     * Destroy a session
     * @param string $id The session ID being destroyed.
     * @param bool   $archiveRemove Remove data from archive or no
     * @return ExtendedPromiseInterface with bool when finished
     */
    public function destroy($id, $archiveRemove = false)
    {
        if (isset($this->keys[$id])) {
            unset($this->keys[$id]);
        }
        $key = $this->getSessionKey($id);
        return $this->cache->delete($key)->then(
            function() use ($id) { return $id; },
            function($error = null) use ($id) {
                $message = sprintf('Failed to destroy session "%s"', $id);
                throw new SessionException($message, 0, $error);
            }
        )->always(function() use ($id, $archiveRemove) {
            return $archiveRemove ? $this->archive->remove($id) : true;
        });
    }

    /**
     * Cleanup old sessions. Timer callback.
     * Sessions that have not updated for the last '$this->gcLifetime' seconds will be archived.
     * Sessions in archive with age bigger than '$this->sessionLifetime' seconds will be removed.
     * @see $gcLifetime
     * @see $gcArchiveLifetime
     * @return void
     */
    public function gc()
    {
        $promises = [];
        if (!$this->_gcIsRunning) {
            $this->_gcIsRunning = true;
            if (!empty($this->keys)) {
                $expiredTime = time() - $this->gcLifetime;
                foreach ($this->keys as $id => $ts) {
                    if ($ts > $expiredTime) {
                        continue;
                    }
                    $promise = $this->read($id)->then(
                        function($data) use ($id) {
                            return $this->archive->set($id, $data);
                        }
                    )->then(
                        function() use ($id) {
                            return $this->destroy($id, false);
                        }
                    )->otherwise(function() { return true; });
                    $promises[] = $promise;
                }
            }
            $promises[] = $this->archive->gc($this->gcArchiveLifetime);
        }
        if (!empty($promises)) {
            all($promises)->always(
                function() {
                    $this->_gcIsRunning = false;
                }
            );
        } else {
            $this->_gcIsRunning = false;
        }
    }

    /**
     * Update timestamp of a session
     * @param string $id The session id
     * @param array  $data
     * The encoded session data. This data is the
     * result of the PHP internally encoding
     * the $_SESSION superglobal to a serialized
     * string and passing it as this parameter.
     * @return ExtendedPromiseInterface  with bool when finished
     */
    public function updateTimestamp($id, $data = null)
    {
        $self = $this;
        return $this->read($id)->then(
            function($dataStored) use ($self, $id, $data) {
                if (isset($data)) {
                    $dataStored = $data;
                }
                return $self->write($id, $dataStored);
            }
        )->then(
            function() { return true; }
        );
    }

    /**
     * Extract session data from record
     * @param array $sessionRecord
     * @param bool  $unserialize
     * @return array
     */
    public function extractData($sessionRecord = [], $unserialize = false) {
        $data = isset($sessionRecord[$this->dataKey]) ? $sessionRecord[$this->dataKey] : [];
        return $unserialize && is_string($data) ? $this->unserializeData($data) : $data;
    }

    /**
     * Extract session timestamp from record
     * @param array $record
     * @return integer|null
     */
    protected function extractTimestamp($record = []) {
        return isset($record[$this->timestampKey]) ? $record[$this->timestampKey] : null;
    }

    /**
     * Get cache session key
     * @param string $sessionId
     * @return string
     */
    protected function getSessionKey($sessionId) {
        return $this->keyPrefix . trim($sessionId);
    }

    /**
     * Get data from cache storage
     * @param string $key
     * @return ExtendedPromiseInterface
     */
    protected function getDataFromCache($key) {
        $self = $this;
        return (new Promise(function($r, $c) use ($self, $key) {
            $self->cache
                ->get($key)
                ->then(function($data) {
                    return $data !== null ? $data : reject(null);
                })->then(function($data) use ($r) {
                    $r($data);
                }, function() use ($c, $key) {
                    $message = sprintf('Failed to get session data from cache for "%s"', $key);
                    $c(new SessionException($message));
                });
        }));
    }

    /**
     * @param string $key
     * @param array  $record
     * @return ExtendedPromiseInterface
     */
    protected function writeDataToCache($key, $record) {
        $ts = $this->extractTimestamp($record);
        $record[$this->timestampKey] = isset($ts) ? $ts : time();

        $cache = $this->cache;
        return new Promise(function($r, $c) use ($cache, $key, $record) {
            $cache->set($key, $record)->then(
                function() use ($r) {
                    $r(true);
                },
                function() use (&$c, $key) {
                    $message = sprintf('Failed to write session data to cache for "%s"', $key);
                    $c(new SessionException($message));
                }
            );
        });
    }

    /**
     * Pack record
     * @param array $data
     * @return array
     */
    protected function packRecord($data) {
        $record = [];
        $dKey = $this->dataKey;
        $tKey = $this->timestampKey;
        if (isset($data[$tKey])) {
            unset($data[$tKey]);
        }
        if (isset($data[$dKey])) {
            $record[$dKey] = $data[$dKey];
        } else {
            $record[$dKey] = $data;
        }
        $record[$this->timestampKey] = time();
        $record[$dKey] = !is_string($record[$dKey])
            ? $this->serializeData($record[$dKey])
            : $record[$dKey];
        return $record;
    }
}


<?php namespace RestGalleries\Http\Plugins;

use RestGalleries\Auth\OhmyAuth\OhmyAuth;
use RestGalleries\Http\Plugins\RequestPluginAdapter;

/**
 * Normalizes the auth plugin of the Http client, receives input data to configure the plugin.
 */
abstract class Auth implements RequestPluginAdapter
{
    /**
     * Stores the authentication credentials as an array.
     *
     * @var array
     */
    protected $credentials;

    /**
     * Stores the auth protocol name.
     *
     * @var string
     */
    protected $protocol;

    /**
     * Initializes processing of plugin data.
     *
     * @param  array $credentials
     * @return void
     */
    public function __construct(array $credentials)
    {
        $this->processPluginData($credentials);
    }

    /**
     * Receives data to configure the plugin, then verifies and separates them into variables.
     *
     * @param  array $credentials
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function processPluginData($credentials)
    {
        if (! $protocol = OhmyAuth::getAuthProtocol($credentials)) {
            throw new \InvalidArgumentException('Credential keys are invalid.');
        }

        $this->credentials = $credentials;
        $this->protocol    = $protocol;

    }

    /**
     * Calls another method that determine which plugin will be returned and return it.
     *
     * @return object
     */
    public function add()
    {
        return $this->getAuthExtension();
    }

    /**
     * Calls method that will return the auth extension that specified in the property $protocol.
     *
     * @return object
     */
    protected function getAuthExtension()
    {
        $method = 'get';
        $method .= $this->protocol;
        $method .= 'Extension';

        return call_user_func_array([$this, $method], [null]);

    }

    abstract protected function getOauth1Extension();
    abstract protected function getOauth2Extension();

}


<?php

namespace Siwayll\Kapow;

/**
 * class Exception
 */
class Exception extends \Exception implements KapowInterface
{
    const VARIABLE_REGEX = "/(?<variable>\{[a-zA-Z0-9\_]+\})/";

    /**
     * Constructor
     *
     * @param integer $code
     * @param string  $message
     */
    public function __construct(
        string $message = '',
        int $code = 0
    ) {
        parent::__construct($message, $code);
    }

    /**
     * Set code
     *
     * @param integer $code
     *
     * @return self
     */
    final public function setCode(int $code): self
    {
        $this->code = $code;

        return $this;
    }

    /**
     * Set message
     *
     * @param string $message
     *
     * @return self
     */
    final public function setMessage(string $message): self
    {
        $this->message = $message;

        return $this;
    }

    /**
     * Get message with variables
     *
     * @throws \Exception
     *
     * @return string
     */
    final public function getMessageWithVariables(): string
    {
        if (empty($this->message)) {
            throw new \Exception(sprintf(
                'Exception %s does not have a message',
                get_class($this)
            ), Level::CRITICAL);
        }

        $message = $this->message;

        preg_match_all(self::VARIABLE_REGEX, $message, $matches);

        foreach ($matches['variable'] as $variable) {
            $variableName = substr($variable, 1, -1);

            if (!isset($this->$variableName)) {
                throw new \Exception(sprintf(
                    'Variable "%s" for exception "%s" not found',
                    $variableName,
                    get_class($this)
                ), Level::CRITICAL);
            }

            if (!is_string($this->$variableName)) {
                throw new \Exception(sprintf(
                    'Variable "%s" for exception "%s" must be a string, %s found',
                    $variableName,
                    get_class($this),
                    gettype($this->$variableName)
                ), Level::CRITICAL);
            }

            $message = str_replace($variable, $this->$variableName, $message);
        }

        return $message;
    }
}


<?php

declare(strict_types=1);

namespace Psi\Component\ObjectAgent;

use Psi\Component\ObjectAgent\Event\ObjectEvent;
use Psi\Component\ObjectAgent\Query\Query;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class EventDispatchingAgent implements AgentInterface
{
    private $dispatcher;
    private $agent;

    public function __construct(
        AgentInterface $agent,
        EventDispatcherInterface $dispatcher
    ) {
        $this->agent = $agent;
        $this->dispatcher = $dispatcher;
    }

    /**
     * {@inheritdoc}
     */
    public function getCapabilities(): Capabilities
    {
        return $this->agent->getCapabilities();
    }

    /**
     * {@inheritdoc}
     */
    public function find($identifier, string $class = null)
    {
        return $this->agent->find($identifier, $class);
    }

    /**
     * {@inheritdoc}
     */
    public function findMany(array $identifiers, string $class = null)
    {
        return $this->agent->findMany($identifiers, $class);
    }

    /**
     * {@inheritdoc}
     */
    public function persist($object)
    {
        $this->dispatch(Events::PRE_PERSIST, $object);
        $this->agent->persist($object);
        $this->dispatch(Events::POST_PERSIST, $object);
    }

    /**
     * {@inheritdoc}
     */
    public function query(Query $query): \Traversable
    {
        return $this->agent->query($query);
    }

    /**
     * {@inheritdoc}
     */
    public function queryCount(Query $query): int
    {
        return $this->agent->queryCount($query);
    }

    /**
     * {@inheritdoc}
     */
    public function remove($object)
    {
        $this->dispatch(Events::PRE_REMOVE, $object);
        $this->agent->remove($object);
        $this->dispatch(Events::POST_REMOVE, $object);
    }

    /**
     * {@inheritdoc}
     */
    public function flush()
    {
        $this->agent->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function getIdentifier($object)
    {
        return $this->agent->getIdentifier($object);
    }

    /**
     * {@inheritdoc}
     */
    public function supports(string $class): bool
    {
        return $this->agent->supports($class);
    }

    /**
     * {@inheritdoc}
     */
    public function setParent($object, $parent)
    {
        return $this->agent->setParent($object, $parent);
    }

    /**
     * {@inheritdoc}
     */
    public function getCanonicalClassFqn(string $classFqn): string
    {
        return $this->agent->getCanonicalClassFqn($classFqn);
    }

    private function dispatch(string $eventName, $object)
    {
        $this->dispatcher->dispatch($eventName, new ObjectEvent($this->agent, $object));
    }
}


<?php
namespace Jumpstart\Battery;

/**
 * The core plugin class.
 *
 * This is used to define internationalization and load components.
 *
 * Also maintains the unique identifier of this plugin as well as the current
 * version of the plugin.
 *
 * @since           1.0.0
 * @package         Jumpstart\Battery
 * @author          Moritz Kornher <mail@moritzkornher.de>
 */
/**
 * Class Plugin
 * @package Jumpstart\Battery
 */
abstract class Plugin extends Component
{
    /**
     * The unique identifier of this plugin.
     *
     * @since       1.0.0
     * @access      protected
     * @var         string $slug The string used to uniquely identify this plugin.
     */
    protected $slug;

    /**
     * The current version of the plugin.
     *
     * @since       1.0.0
     * @access      protected
     * @var         string $version The current version of the plugin.
     */
    protected $version;

    /**
     * The plugin basename.
     *
     * @since       1.0.0
     * @access      protected
     * @var         string $basename The basename of the plugin.
     */
    protected $basename;

    /**
     * The base path of the plugin.
     *
     * @since       1.0.0
     * @access      protected
     * @var         string $path The base path of the plugin.
     */
    protected $path;

    /**
     * The loader that's responsible for maintaining and registering all hooks that power
     * the plugin.
     *
     * @since       1.0.0
     * @access      protected
     * @var         Loader $loader Maintains and registers all hooks for the plugin.
     */
    protected $loader;

    /**
     * Define the core functionality of the plugin.
     *
     * Set the plugin name and the plugin version that can be used throughout the plugin.
     * Define the locale and load the components.
     *
     * @since       {{version}}
     * @access      public
     * @param string $filename
     */
    public function __construct($filename)
    {
        $this->basename = plugin_basename($filename);
        $this->path = dirname($filename);
        $this->loader = new Loader($this->getSlug(), $this->getVersion(), $this->getBasename());
        parent::__construct();
    }

    /**
     * Run the loader to execute all of the hooks with WordPress.
     *
     * @since       1.0.0
     */
    public function jumpstart()
    {
        $this->getLoader()->action('activate_' . $this->getBasename(), array($this, 'activate'));
        $this->getLoader()->action('deactivate_' . $this->getBasename(), array($this, 'deactivate'));
        $this->getLoader()->action('uninstall_' . $this->getBasename(), array($this, 'uninstall'));
        $this->getLoader()->run();
    }

    /**
     * The plugin slug used to uniquely identify it within the context of
     * WordPress and to define internationalization functionality.
     *
     * @since       1.0.0
     * @return      string      The plugin slug.
     */
    public function getSlug()
    {
        return $this->slug;
    }

    /**
     * Retrieve the version number of the plugin.
     *
     * @since       1.0.0
     * @return      string      The version number of the plugin.
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Retrieve the basename of the plugin.
     *
     * @since       1.0.0
     * @return      string      The basename of the plugin.
     */
    public function getBasename()
    {
        return $this->basename;
    }

    /**
     * Retrieve the base path of the plugin.
     *
     * @since       1.0.0
     * @return      string      The base path of the plugin.
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * The reference to the class that orchestrates the hooks with the plugin.
     *
     * @since       1.0.0
     * @return      Loader      Orchestrates the hooks of the plugin.
     */
    public function getLoader()
    {
        return $this->loader;
    }

    /**
     * Actions performed when activating the plugin.
     *
     * @since       1.0.0
     * @return      void
     */
    public function activate()
    {
    }

    /**
     * Actions performed when deactivating the plugin.
     *
     * @since       1.0.0
     * @return      void
     */
    public function deactivate()
    {
    }

    /**
     * Actions performed when uninstalling the plugin.
     *
     * @since       1.0.0
     * @return      void
     */
    public function uninstall()
    {
    }
}


<?php

namespace Dhii\Iterator;

use Dhii\Iterator\Exception\IterationException;
use Dhii\Util\String\StringableInterface as Stringable;
use Exception as RootException;

/**
 * Common functionality for objects that can create iteration exceptions.
 *
 * @since [*next-version*]
 */
trait CreateIterationExceptionCapableTrait
{
    /**
     * Creates a new iteration exception instance.
     *
     * @since [*next-version*]
     *
     * @param string|Stringable|null  $message   The error message, if any.
     * @param int|null                $code      The error code, if any.
     * @param RootException|null      $previous  The previous exception for chaining, if any.
     * @param IterationInterface|null $iteration The iteration instance, if any.
     *
     * @return IterationException The created exception instance.
     */
    protected function _createIterationException(
        $message = null,
        $code = null,
        RootException $previous = null,
        IterationInterface $iteration = null
    ) {
        return new IterationException($message, $code, $previous, $iteration);
    }
}


<?php

namespace Reaction\Widgets\Bootstrap;

use Reaction\Assets\BootstrapPluginAsset;
use Reaction\Helpers\Html;
use Reaction;
use Reaction\Helpers\ArrayHelper;

class NavBar extends Widget
{
    /**
     * @var array the HTML attributes for the widget container tag. The following special options are recognized:
     *
     * - tag: string, defaults to "nav", the name of the container tag.
     *
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $options = [];
    /**
     * @var array the HTML attributes for the container tag. The following special options are recognized:
     *
     * - tag: string, defaults to "div", the name of the container tag.
     *
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $containerOptions = [];
    /**
     * @var string|boolean the text of the brand or false if it's not used. Note that this is not HTML-encoded.
     * @see http://getbootstrap.com/components/#navbar
     */
    public $brandLabel = false;
    /**
     * @var array|string|boolean $url the URL for the brand's hyperlink tag. This parameter will be processed by [[\yii\helpers\Url::to()]]
     * and will be used for the "href" attribute of the brand link. Default value is false that means
     * [[\Reaction\RequestApplicationInterface::homeUrl]] will be used.
     * You may set it to `null` if you want to have no link at all.
     */
    public $brandUrl = false;
    /**
     * @var array the HTML attributes of the brand link.
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $brandOptions = [];
    /**
     * @var string text to show for screen readers for the button to toggle the navbar.
     */
    public $screenReaderToggleText;
    /**
     * @var boolean whether the navbar content should be included in an inner div container which by default
     * adds left and right padding. Set this to false for a 100% width navbar.
     */
    public $renderInnerContainer = true;
    /**
     * @var array the HTML attributes of the inner container.
     * @see \Reaction\Helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
     */
    public $innerContainerOptions = [];

    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();
        $this->clientOptions = false;
        if (empty($this->options['class'])) {
            Html::addCssClass($this->options, ['navbar', 'navbar-light bg-light']);
            Html::addCssClass($this->options, ['expand' => 'navbar-expand-md']);
        } else {
            Html::addCssClass($this->options, ['widget' => 'navbar']);
        }
        $options = $this->options;
        $tag = ArrayHelper::remove($options, 'tag', 'nav');
        echo $this->htmlHlp->beginTag($tag, $options);
        if ($this->renderInnerContainer) {
            if (!isset($this->innerContainerOptions['class'])) {
                Html::addCssClass($this->innerContainerOptions, 'container');
            }
            echo $this->htmlHlp->beginTag('div', $this->innerContainerOptions);
        }
        if (!isset($this->containerOptions['id'])) {
            $this->containerOptions['id'] = "{$this->options['id']}-collapse";
        }
        if ($this->brandLabel !== false) {
            Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']);
            echo $this->htmlHlp->a($this->brandLabel, $this->brandUrl === false ? $this->app->getHomeUrl() : $this->brandUrl, $this->brandOptions);
        }
        echo $this->renderToggleButton();
        Html::addCssClass($this->containerOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']);
        $options = $this->containerOptions;
        $tag = ArrayHelper::remove($options, 'tag', 'div');
        echo $this->htmlHlp->beginTag($tag, $options);
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div');
        echo Html::endTag($tag);
        if ($this->renderInnerContainer) {
            echo Html::endTag('div');
        }
        $tag = ArrayHelper::remove($this->options, 'tag', 'nav');
        echo Html::endTag($tag);
        BootstrapPluginAsset::register($this->getView());
    }

    /**
     * Renders collapsible toggle button.
     * @return string the rendering toggle button.
     */
    protected function renderToggleButton()
    {
        $icon = $this->htmlHlp->tag('span', '', ['class' => 'navbar-toggler-icon']);
        $screenReader = isset($this->screenReaderToggleText)
            ? "{$this->screenReaderToggleText}"
            : '';

        return $this->htmlHlp->button("{$screenReader}\n{$icon}", [
            'class' => 'navbar-toggler',
            'data-toggle' => 'collapse',
            'data-target' => "#{$this->containerOptions['id']}",
        ]);
    }
}


<?php

declare(strict_types=1);

/**
 * States.
 *
 * LICENSE
 *
 * This source file is subject to the MIT license and the version 3 of the GPL3
 * license that are bundled with this package in the folder licences
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to richarddeloge@gmail.com so we can send you a copy immediately.
 *
 *
 * @copyright   Copyright (c) 2009-2017 Richard Déloge (richarddeloge@gmail.com)
 *
 * @link        http://teknoo.software/states Project website
 *
 * @license     http://teknoo.software/license/mit         MIT License
 * @author      Richard Déloge <richarddeloge@gmail.com>
 */

namespace Teknoo\States\LifeCycle\Scenario;

use Teknoo\States\LifeCycle\Event\EventInterface;
use Teknoo\States\LifeCycle\Observing\ObservedInterface;
use Teknoo\States\LifeCycle\StatedClass\LifeCyclableInterface;

/**
 * Class Scenario
 * Default implementation of scenario class to allow developper to write interaction with a stated class
 * and its instances with others components of the application.
 *
 *
 * @copyright   Copyright (c) 2009-2017 Richard Déloge (richarddeloge@gmail.com)
 *
 * @link        http://teknoo.software/states Project website
 *
 * @license     http://teknoo.software/license/mit         MIT License
 * @author      Richard Déloge <richarddeloge@gmail.com>
 */
class Scenario implements ScenarioInterface
{
    /**
     * @var callable
     */
    private $callback;

    /**
     * @var string[]
     */
    private $eventsNamesList;

    /**
     * @var string[]
     */
    private $neededIncomingStatesList;

    /**
     * @var string[]
     */
    private $neededOutgoingStatesList;

    /**
     * @var string[]
     */
    private $neededStatesList;

    /**
     * @var string[]
     */
    private $forbiddenStatesList;

    /**
     * @var string
     */
    private $neededStatedClassName;

    /**
     * @var ObservedInterface
     */
    private $neededStatedObject;

    /**
     * {@inheritdoc}
     */
    public function configure(ScenarioBuilder $scenarioBuilder): ScenarioInterface
    {
        $this->eventsNamesList = $scenarioBuilder->getEventNamesList();
        $this->neededIncomingStatesList = $scenarioBuilder->getNeededIncomingStatesList();
        $this->neededOutgoingStatesList = $scenarioBuilder->getNeededOutgoingStatesList();
        $this->neededStatesList = $scenarioBuilder->getNeededStatesList();
        $this->forbiddenStatesList = $scenarioBuilder->getForbiddenStatesList();
        $this->neededStatedClassName = $scenarioBuilder->getStatedClassName();
        $this->neededStatedObject = $scenarioBuilder->getObserved();
        $this->callback = $scenarioBuilder->getCallable();

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getEventsNamesList(): array
    {
        return $this->eventsNamesList;
    }

    /**
     * @return string[]
     */
    public function listNeededIncomingStates(): array
    {
        return $this->neededIncomingStatesList;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkNeededIncomingStates(EventInterface $event): bool
    {
        $neededIncomingStates = $this->listNeededIncomingStates();
        if (empty($neededIncomingStates)) {
            return true;
        }

        $incomingStateList = $event->getIncomingStates();

        return empty(\array_diff($neededIncomingStates, $incomingStateList));
    }

    /**
     * @return string[]
     */
    public function listNeededOutgoingStates(): array
    {
        return $this->neededOutgoingStatesList;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkNeededOutgoingStates(EventInterface $event): bool
    {
        $neededOutgoingStates = $this->listNeededOutgoingStates();
        if (empty($neededOutgoingStates)) {
            return true;
        }

        $outgoingStatesList = $event->getOutgoingStates();

        return empty(\array_diff($neededOutgoingStates, $outgoingStatesList));
    }

    /**
     * @return string[]
     */
    public function listNeededStates(): array
    {
        return $this->neededStatesList;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkNeededStates(EventInterface $event): bool
    {
        $neededStatesList = $this->listNeededStates();
        if (empty($neededStatesList)) {
            return true;
        }

        $enabledStatesList = $event->getObject()->listEnabledStates();

        return empty(\array_diff($neededStatesList, $enabledStatesList));
    }

    /**
     * @return string[]
     */
    public function listForbiddenStates(): array
    {
        return $this->forbiddenStatesList;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkForbiddenStates(EventInterface $event): bool
    {
        $forbiddenStatesList = $this->listForbiddenStates();
        if (empty($forbiddenStatesList)) {
            return true;
        }

        $enabledStatesList = $event->getObject()->listEnabledStates();

        return empty(\array_intersect($forbiddenStatesList, $enabledStatesList));
    }

    /**
     * @return string
     */
    public function getNeededStatedClass(): string
    {
        return $this->neededStatedClassName;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkNeededStatedClass(EventInterface $event): bool
    {
        $neededStatedClass = $this->getNeededStatedClass();
        if (empty($neededStatedClass)) {
            return true;
        }

        return $neededStatedClass === $event->getObserved()->getStatedClassName();
    }

    /**
     * {@inheritdoc}
     */
    public function getNeededStatedObject()
    {
        return $this->neededStatedObject;
    }

    /**
     * @param EventInterface $event
     *
     * @return bool
     */
    private function checkNeededStatedObject(EventInterface $event): bool
    {
        $neededStatedObject = $this->getNeededStatedObject();
        if (!$neededStatedObject instanceof LifeCyclableInterface) {
            return true;
        }

        return $neededStatedObject === $event->getObject();
    }

    /**
     * {@inheritdoc}
     */
    public function isAllowedToRun(EventInterface $event): bool
    {
        if (!$this->checkNeededIncomingStates($event)) {
            return false;
        }

        if (!$this->checkNeededOutgoingStates($event)) {
            return false;
        }

        if (!$this->checkNeededStates($event)) {
            return false;
        }

        if (!$this->checkForbiddenStates($event)) {
            return false;
        }

        if (!$this->checkNeededStatedClass($event)) {
            return false;
        }

        if (!$this->checkNeededStatedObject($event)) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function __invoke(EventInterface $event)
    {
        if ($this->isAllowedToRun($event)) {
            $callback = $this->callback;
            $callback($event);
        }
    }
}


<?php namespace Ordercloud\Entities\Payments;

use JsonSerializable;
use Ordercloud\Entities\Organisations\OrganisationShort;
use Ordercloud\Entities\Users\DisplayUser;

class Payment implements JsonSerializable
{
    const PAYMENT_GATEWAY_MYGATE_ZA = "MYGATE_ZA";
    const PAYMENT_GATEWAY_PAYU_ZA = "PAYU_ZA";

    /** @var integer */
    private $id;
    /** @var PaymentStatus */
    private $lastPaymentStatus;
    /** @var string */
    private $gatewayTransactionId;
    /** @var string */
    private $requestId;
    /** @var DisplayUser */
    private $requestedByUser;
    /** @var OrganisationShort */
    private $requestedByOrganisation;
    /** @var string */
    private $currencyCode;
    /** @var float */
    private $amount;
    /** @var string */
    private $paymentMethod;
    /** @var string */
    private $gateway;
    /**
     * @var string
     */
    private $creationDate;

    public function __construct($id, PaymentStatus $lastPaymentStatus, $gatewayTransactionId, $requestId, DisplayUser $requestedByUser, OrganisationShort $requestedByOrganisation, $currencyCode, $amount, $paymentMethod, $gateway, $creationDate)
    {
        $this->id = $id;
        $this->lastPaymentStatus = $lastPaymentStatus;
        $this->gatewayTransactionId = $gatewayTransactionId;
        $this->requestId = $requestId;
        $this->requestedByUser = $requestedByUser;
        $this->requestedByOrganisation = $requestedByOrganisation;
        $this->currencyCode = $currencyCode;
        $this->amount = $amount;
        $this->paymentMethod = $paymentMethod;
        $this->gateway = $gateway;
        $this->creationDate = $creationDate;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return PaymentStatus
     */
    public function getLastPaymentStatus()
    {
        return $this->lastPaymentStatus;
    }

    /**
     * @return string
     */
    public function getGatewayTransactionId()
    {
        return $this->gatewayTransactionId;
    }

    /**
     * @return string
     */
    public function getRequestId()
    {
        return $this->requestId;
    }

    /**
     * @return DisplayUser
     */
    public function getRequestedByUser()
    {
        return $this->requestedByUser;
    }

    /**
     * @return OrganisationShort
     */
    public function getRequestedByOrganisation()
    {
        return $this->requestedByOrganisation;
    }

    /**
     * @return string
     */
    public function getCurrencyCode()
    {
        return $this->currencyCode;
    }

    /**
     * @return float
     */
    public function getAmount()
    {
        return $this->amount;
    }

    /**
     * @return string
     */
    public function getPaymentMethod()
    {
        return $this->paymentMethod;
    }

    /**
     * @return string
     */
    public function getGateway()
    {
        return $this->gateway;
    }

    /**
     * @return string
     */
    public function getCreationDate()
    {
        return $this->creationDate;
    }

    /**
     * Specify data which should be serialized to JSON
     */
    public function jsonSerialize()
    {
        return [
            'id' => $this->getId(),
            'lastPaymentStatus' => $this->getLastPaymentStatus(),
            'gatewayTransactionId' => $this->getGatewayTransactionId(),
            'requestId' => $this->getRequestId(),
            'requestedByUser' => $this->getRequestedByUser(),
            'requestedByOrganisation' => $this->getRequestedByOrganisation(),
            'currencyCode' => $this->getCurrencyCode(),
            'amount' => $this->getAmount(),
            'paymentMethod' => $this->getPaymentMethod(),
            'gateway' => $this->getGateway(),
            'creationDate' => $this->getCreationDate(),
        ];
    }
}


<?php
/**
 * Таблица привязок
 *
 * @version ${product.version}
 *
 * @copyright 2013, Михаил Красильников <m.krasilnikov@yandex.ru>
 * @license http://www.gnu.org/licenses/gpl.txt	GPL License 3
 * @author Михаил Красильников <m.krasilnikov@yandex.ru>
 *
 * Данная программа является свободным программным обеспечением. Вы
 * вправе распространять ее и/или модифицировать в соответствии с
 * условиями версии 3 либо (по вашему выбору) с условиями более поздней
 * версии Стандартной Общественной Лицензии GNU, опубликованной Free
 * Software Foundation.
 *
 * Мы распространяем эту программу в надежде на то, что она будет вам
 * полезной, однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ, в том
 * числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ и ПРИГОДНОСТИ ДЛЯ
 * ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ. Для получения более подробной
 * информации ознакомьтесь со Стандартной Общественной Лицензией GNU.
 *
 * Вы должны были получить копию Стандартной Общественной Лицензии
 * GNU с этой программой. Если Вы ее не получили, смотрите документ на
 * <http://www.gnu.org/licenses/>
 *
 * @package ORM
 */

/**
 * Таблица привязок
 *
 * @package ORM
 * @since 3.00
 */
class ORM_Table_Bindings extends ORM_Table
{
    /**
     * Основная таблица
     *
     * @var ORM_Table
     */
    private $baseTable;

    /**
     * Поле, по которому производится связывание
     * @var string
     */
    private $property;

    /**
     * Конструктор
     *
     * @param ORM_Table $baseTable  таблица, хранящая базовые объекты
     * @param string    $property   свойство, привязки к которому хранит таблица
     */
    public function __construct(ORM_Table $baseTable, $property)
    {
        assert('is_string($property)');
        assert('"" != $property');
        $this->baseTable = $baseTable;
        $this->property = $property;
        parent::__construct($baseTable->getPlugin(), $baseTable->getDriver());
    }

    /**
     * Возвращает имя поля, относящегося к базовой таблице
     *
     * @return string
     */
    public function getSourceField()
    {
        return preg_replace('/^.*?_/', '', $this->baseTable->getName());
    }

    /**
     * Описание таблицы
     */
    protected function setTableDefinition()
    {
        $this->setTableName($this->baseTable->getName() . '_' . $this->property);

        $sourceField = $this->getSourceField();
        $this->hasColumns(array(
            $sourceField => array(
                'type' => 'integer',
                'unsigned' => true
            ),
            $this->property => array(
                'type' => 'integer',
                'unsigned' => true
            ),
        ));
        $this->setPrimaryKey(array($sourceField, $this->property));
    }
}


<?php

/*
 * This file is part of the FiveLab Reflection package.
 *
 * (c) FiveLab <mail@fivelab.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace FiveLab\Component\Reflection;

use Doctrine\Common\Annotations\Reader;

/**
 * Reflection helper
 *
 * @author Vitaliy Zhuk <v.zhuk@fivelab.org>
 */
final class Reflection
{
    const TYPE_CLASS        = 0b00000001;
    const TYPE_OBJECT       = 0b00000010;

    /**
     * @var array|\ReflectionClass[]
     */
    private static $classReflections = array();

    /**
     * @var array|\ReflectionObject[]
     */
    private static $objectReflections = array();

    /**
     * Disable constructor
     */
    private function __construct()
    {
    }

    /**
     * Load class reflection
     *
     * @param string|object $class
     *
     * @return \ReflectionClass
     */
    public static function loadClassReflection($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        if (isset(self::$classReflections[$class])) {
            return self::$classReflections[$class];
        }

        $reflection = new \ReflectionClass($class);

        self::$classReflections[$class] = $reflection;

        return $reflection;
    }

    /**
     * Load method reflection
     *
     * @param string|object $class
     * @param string        $method
     *
     * @return \ReflectionMethod
     */
    public static function loadMethodReflection($class, $method)
    {
        $reflectionClass = self::loadClassReflection($class);

        return $reflectionClass->getMethod($method);
    }

    /**
     * Load object reflection
     *
     * @param object $object
     *
     * @return \ReflectionObject
     */
    public static function loadObjectReflection($object)
    {
        if (!is_object($object)) {
            throw new \InvalidArgumentException(sprintf(
                'Could not load reflection for non object. Given: "%s".',
                gettype($object)
            ));
        }

        $hash = spl_object_hash($object);

        if (isset(self::$objectReflections[$hash])) {
            return self::$objectReflections[$hash];
        }

        $reflection = new \ReflectionObject($object);

        self::$objectReflections[$hash] = $reflection;

        return $reflection;
    }

    /**
     * Load property reflection
     *
     * @param object $object
     * @param string $property
     * @param bool   $searchInParents
     *
     * @return \ReflectionProperty
     *
     * @throws \ReflectionException
     */
    public static function loadPropertyReflection($object, $property, $searchInParents = true)
    {
        $reflection = self::loadClassReflection($object);

        if (!$searchInParents) {
            return $reflection->getProperty($property);
        }

        $exception = null;

        do {
            try {
                $reflectionProperty = $reflection->getProperty($property);

                return $reflectionProperty;
            } catch (\ReflectionException $e) {
                if (!$exception) {
                    $exception = $e;
                }
            }
        } while ($reflection = $reflection->getParentClass());

        throw $exception;
    }

    /**
     * Get called method from abstract reflection function
     *
     * @param \ReflectionFunctionAbstract $method
     * @param bool                        $closureInfo
     *
     * @return string
     */
    public static function getCalledMethod(\ReflectionFunctionAbstract $method, $closureInfo = true)
    {
        if ($method->isClosure()) {
            if ($closureInfo) {
                return sprintf(
                    'Closure [%s:%d]',
                    $method->getFileName(),
                    $method->getStartLine()
                );
            }

            return 'Closure';
        }

        if ($method instanceof \ReflectionMethod) {
            return sprintf(
                '%s::%s',
                $method->getDeclaringClass()->getName(),
                $method->getName()
            );
        }

        return $method->getName();
    }

    /**
     * Get class properties
     *
     * @param string $class
     * @param bool   $inParents
     * @param int    $filter
     *
     * @return \ReflectionProperty[]
     */
    public static function getClassProperties($class, $inParents = false, $filter = null)
    {
        if ($filter === null) {
            $filter = \ReflectionProperty::IS_PRIVATE
                | \ReflectionProperty::IS_PROTECTED
                | \ReflectionProperty::IS_PUBLIC;
        }

        $reflection = self::loadClassReflection($class);

        if (!$inParents) {
            return $reflection->getProperties($filter);
        }

        $properties = [];

        do {
            $properties = array_merge(
                $reflection->getProperties($filter),
                $properties
            );
        } while ($reflection = $reflection->getParentClass());

        return $properties;
    }

    /**
     * Set value to property
     *
     * @param object|string $object
     * @param string        $property
     * @param mixed  $value
     */
    public static function setPropertyValue($object, $property, $value)
    {
        $ref = self::loadClassReflection($object);

        $refProperty = $ref->getProperty($property);

        if (!$refProperty->isPublic()) {
            $refProperty->setAccessible(true);
        }

        if ($refProperty->isStatic()) {
            $refProperty->setValue($value);
        } else {
            $refProperty->setValue($object, $value);
        }
    }

    /**
     * Set value to properties
     *
     * @param object|string $object
     * @param array         $properties
     */
    public static function setPropertiesValue($object, array $properties)
    {
        foreach ($properties as $name => $value) {
            self::setPropertyValue($object, $name, $value);
        }
    }

    /**
     * Get property value
     *
     * @param object|string $object
     * @param string        $property
     *
     * @return mixed
     */
    public static function getPropertyValue($object, $property)
    {
        $ref = self::loadClassReflection($object);

        $refProperty = $ref->getProperty($property);

        if (!$refProperty->isPublic()) {
            $refProperty->setAccessible(true);
        }

        if ($refProperty->isStatic()) {
            $value = $refProperty->getValue();
        } else {
            $value = $refProperty->getValue($object);
        }

        return $value;
    }

    /**
     * Load class annotations
     *
     * @param Reader        $reader
     * @param string|object $class
     * @param bool          $inParents
     *
     * @return array
     */
    public static function loadClassAnnotations(Reader $reader, $class, $inParents = false)
    {
        $reflection = self::loadClassReflection($class);

        if (!$inParents) {
            return $reader->getClassAnnotations($reflection);
        }

        $annotations = [];

        do {
            $classAnnotations = $reader->getClassAnnotations($reflection);

            foreach ($classAnnotations as $classAnnotation) {
                if (!isset($annotations[get_class($classAnnotation)])) {
                    $annotations[get_class($classAnnotation)] = $classAnnotation;
                }
            }
        } while ($reflection = $reflection->getParentClass());

        return array_values($annotations);
    }

    /**
     * Clear internal storage
     *
     * @param int $mode
     */
    public static function clear($mode = null)
    {
        if (is_numeric($mode)) {
            $mode = self::TYPE_CLASS | self::TYPE_OBJECT;
        }

        if ($mode & self::TYPE_CLASS) {
            self::$classReflections = array();
        }

        if ($mode & self::TYPE_OBJECT) {
            self::$objectReflections = array();
        }
    }
}