<?php
/*
* This file is part of Mandango.
*
* (c) Pablo Díez <pablodip@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Mandango\Mondator;
use Mandango\Mondator\Definition\Method;
use Mandango\Mondator\Definition\Property;
/**
* ClassExtension is the base class for class extensions.
*
* @author Pablo Díez <pablodip@gmail.com>
*
* @api
*/
abstract class ClassExtension
{
private $options;
private $requiredOptions;
protected $definitions;
protected $class;
protected $configClasses;
protected $configClass;
protected $newClassExtensions;
protected $newConfigClasses;
protected $twig;
protected $twigTempDir;
/**
* Constructor.
*
* @param array $options An array of options.
*
* @api
*/
public function __construct(array $options = array())
{
$this->options = array();
$this->requiredOptions = array();
$this->setUp();
foreach ($options as $name => $value) {
$this->setOption($name, $value);
}
// required options
if ($diff = array_diff($this->requiredOptions, array_keys($options))) {
throw new \RuntimeException(sprintf('%s requires the options: "%s".', get_class($this), implode(', ', $diff)));
}
}
/**
* Set up the extension.
*
* @api
*/
protected function setUp()
{
}
/**
* Add an option.
*
* @param string $name The option name.
* @param mixed $defaultValue The default value (optional, null by default).
*
* @api
*/
protected function addOption($name, $defaultValue = null)
{
$this->options[$name] = $defaultValue;
}
/**
* Add options.
*
* @param array $options An array with options (name as key and default value as value).
*
* @api
*/
protected function addOptions(array $options)
{
foreach ($options as $name => $defaultValue) {
$this->addOption($name, $defaultValue);
}
}
/**
* Add a required option.
*
* @param string $name The option name.
*
* @api
*/
protected function addRequiredOption($name)
{
$this->addOption($name);
$this->requiredOptions[] = $name;
}
/**
* Add required options.
*
* @param array $options An array with the name of the required option as value.
*
* @api
*/
protected function addRequiredOptions(array $options)
{
foreach ($options as $name) {
$this->addRequiredOption($name);
}
}
/**
* Returns if exists an option.
*
* @param string $name The name.
*
* @return bool Returns true if the option exists, false otherwise.
*
* @api
*/
public function hasOption($name)
{
return array_key_exists($name, $this->options);
}
/**
* Set an option.
*
* @param string $name The name.
* @param mixed $value The value.
*
* @throws \InvalidArgumentException If the option does not exists.
*
* @api
*/
public function setOption($name, $value)
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name));
}
$this->options[$name] = $value;
}
/**
* Returns the options.
*
* @return array The options.
*
* @api
*/
public function getOptions()
{
return $this->options;
}
/**
* Return an option.
*
* @param string $name The name.
*
* @return mixed The value of the option.
*
* @throws \InvalidArgumentException If the options does not exists.
*
* @api
*/
public function getOption($name)
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name));
}
return $this->options[$name];
}
/**
* New class extensions process.
*
* @param string $class The class.
* @param \ArrayObject $configClasses The config classes.
* @param \ArrayObject $newClassExtensions The new class extensions.
*
* @api
*/
public function newClassExtensionsProcess($class, \ArrayObject $configClasses, \ArrayObject $newClassExtensions)
{
$this->class = $class;
$this->configClasses = $configClasses;
$this->configClass = $configClasses[$class];
$this->newClassExtensions = $newClassExtensions;
$this->doNewClassExtensionsProcess();
$this->class = null;
$this->configClasses = null;
$this->configClass = null;
$this->newClassExtensions = null;
}
/**
* Do the new class extensions process.
*
* Here you can add new class extensions.
*
* @api
*/
protected function doNewClassExtensionsProcess()
{
}
/**
* New config classes process.
*
* @param string $class The class.
* @param \ArrayObject $configClasses The config classes.
* @param \ArrayObject $newConfigClasses The new config classes.
*
* @api
*/
public function newConfigClassesProcess($class, \ArrayObject $configClasses, \ArrayObject $newConfigClasses)
{
$this->class = $class;
$this->configClasses = $configClasses;
$this->configClass = $configClasses[$class];
$this->newConfigClasses = $newConfigClasses;
$this->doNewConfigClassesProcess();
$this->class = null;
$this->configClasses = null;
$this->configClass = null;
$this->newConfigClasses = null;
}
/**
* Do the new config classes process.
*
* Here you can add new config classes, and change the config classes
* if it is necessary to build the new config classes.
*
* @api
*/
protected function doNewConfigClassesProcess()
{
}
/**
* Process the config class.
*
* @param string $class The class.
* @param \ArrayObject $configClasses The config classes.
*
* @api
*/
public function configClassProcess($class, \ArrayObject $configClasses)
{
$this->class = $class;
$this->configClasses = $configClasses;
$this->configClass = $configClasses[$class];
$this->doConfigClassProcess();
$this->class = null;
$this->configClasses = null;
$this->configClass = null;
}
/**
* Do the config class process.
*
* Here you can modify the config class.
*
* @api
*/
protected function doConfigClassProcess()
{
}
/**
* Process the class.
*
* @param string $class The class.
* @param \ArrayObject $configClasses The config classes.
* @param Mandango\Mondator\Container $container The container.
*
* @api
*/
public function classProcess($class, \ArrayObject $configClasses, Container $container)
{
$this->class = $class;
$this->configClasses = $configClasses;
$this->configClass = $configClasses[$class];
$this->definitions = $container;
$this->doClassProcess();
$this->class = null;
$this->configClasses = null;
$this->configClass = null;
$this->definitions = null;
}
/**
* Do the class process.
*
* @api
*/
protected function doClassProcess()
{
}
/**
* Twig.
*/
protected function processTemplate(Definition $definition, $name, array $variables = array())
{
$twig = $this->getTwig();
$variables['options'] = $this->options;
$variables['class'] = $this->class;
$variables['config_class'] = $this->configClass;
$variables['config_classes'] = $this->configClasses;
$result = $twig->loadTemplate($name)->render($variables);
// properties
$expression = '/
(?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n?
\ \ \ \ (?P<static>static\ )?
(?P<visibility>public|protected|private)
\s
\$
(?P<name>[a-zA-Z0-9_]+)
;
/xU';
preg_match_all($expression, $result, $matches);
for ($i = 0; $i <= count($matches[0]) - 1; $i++) {
$property = new Property($matches['visibility'][$i], $matches['name'][$i], null);
if ($matches['static'][$i]) {
$property->setStatic(true);
}
if ($matches['docComment'][$i]) {
$property->setDocComment($matches['docComment'][$i]);
}
$definition->addProperty($property);
}
// methods
$expression = '/
(?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n
\ \ \ \ (?P<static>static\ )?
(?P<visibility>public|protected|private)
\s
function
\s
(?P<name>[a-zA-Z0-9_]+)
\((?P<arguments>[$a-zA-Z0-9_\\\=\(\), ]*)\)
\n
\ \ \ \ \{
(?P<code>[\s\S]*)
\n\ \ \ \ \}
/xU';
preg_match_all($expression, $result, $matches);
for ($i = 0; $i <= count($matches[0]) - 1; $i++) {
$code = trim($matches['code'][$i], "\n");
$method = new Method($matches['visibility'][$i], $matches['name'][$i], $matches['arguments'][$i], $code);
if ($matches['static'][$i]) {
$method->setStatic(true);
}
if ($matches['docComment'][$i]) {
$method->setDocComment($matches['docComment'][$i]);
}
$definition->addMethod($method);
}
}
public function getTwig()
{
if (null === $this->twig) {
if (!class_exists('Twig_Environment')) {
throw new \RuntimeException('Twig is required to use templates.');
}
$loader = new \Twig_Loader_String();
$twig = new \Twig_Environment($loader, array(
'autoescape' => false,
'strict_variables' => true,
'debug' => true,
'cache' => $this->twigTempDir = sys_get_temp_dir().'Mondator/'.mt_rand(111111, 999999),
));
$this->configureTwig($twig);
$this->twig = $twig;
}
return $this->twig;
}
protected function configureTwig(\Twig_Environment $twig)
{
}
/*
* Tools.
*/
protected function createClassExtensionFromArray(array $data)
{
if (!isset($data['class'])) {
throw new \InvalidArgumentException(sprintf('The extension does not have class.'));
}
return new $data['class'](isset($data['options']) ? $data['options'] : array());
}
private function removeDir($target)
{
$fp = opendir($target);
while (false !== $file = readdir($fp)) {
if (in_array($file, array('.', '..'))) {
continue;
}
if (is_dir($target.'/'.$file)) {
self::removeDir($target.'/'.$file);
} else {
unlink($target.'/'.$file);
}
}
closedir($fp);
rmdir($target);
}
public function __destruct()
{
if ($this->twigTempDir && is_dir($this->twigTempDir)) {
$this->removeDir($this->twigTempDir);
}
}
}