View file cvServiceContainer/cvServiceContainerDumperPhp.php

File size: 9.22Kb
<?php

/**
 * cvServiceContainerDumperPhp
 * 
 * cvServiceContainerDumperPhp dumps a service container as a PHP class.
 * 
 * @package CYBERVILLE
 * @subpackage DI
 * @author Fabien Potencier <[email protected]>
 *         Kochergin Nick <[email protected]>, <http://cyberville-project.ru>
 * @version $Id$
 */
class cvServiceContainerDumperPhp extends cvServiceContainerDumper
{
  /**
   * Dumps the service container as a PHP class.
   *
   * Available options:
   *
   *  * class:      The class name
   *  * base_class: The base class name
   *
   * @param  array  $options An array of options
   *
   * @return string A PHP class representing of the service container
   */
  public function dump(array $options = array())
  {
    $options = array_merge(array(
      'class'      => 'ProjectServiceContainer',
      'base_class' => 'cvServiceSubcontainer',
    ), $options);

    return
      $this->startClass($options['class'], $options['base_class']).
      $this->addConstructor().
      $this->addServices().
      $this->addDefaultParametersMethod().
      $this->endClass()
    ;
  }

  protected function addServiceInclude($id, $definition)
  {
    if (null !== $definition->getFile())
    {
      return sprintf("    require_once %s;\n\n", $this->dumpValue($definition->getFile()));
    }
  }

  protected function addServiceShared($id, $definition)
  {
    if ($definition->isShared())
    {
      return <<<EOF
    if (isset(\$this->shared['$id'])) return \$this->shared['$id'];


EOF;
    }
  }

  protected function addServiceReturn($id, $definition)
  {
    if ($definition->isShared())
    {
      return <<<EOF

    return \$this->shared['$id'] = \$instance;
  }

EOF;
    }
    else
    {
      return <<<EOF

    return \$instance;
  }

EOF;
    }
  }

  protected function addServiceInstance($id, $definition)
  {
    $class = $this->dumpValue($definition->getClass());

    $arguments = array();
    foreach ($definition->getArguments() as $value)
    {
      $arguments[] = $this->dumpValue($value);
    }

    if (null !== $definition->getConstructor())
    {
      return sprintf("    \$instance = call_user_func(array(%s, '%s')%s);\n", $class, $definition->getConstructor(), $arguments ? ', '.implode(', ', $arguments) : '');
    }
    else
    {
      if ($class != "'".$definition->getClass()."'")
      {
        return sprintf("    \$class = %s;\n    \$instance = new \$class(%s);\n", $class, implode(', ', $arguments));
      }
      else
      {
        return sprintf("    \$instance = new %s(%s);\n", $definition->getClass(), implode(', ', $arguments));
      }
    }
  }

  protected function addServiceMethodCalls($id, $definition)
  {
    $calls = '';
    foreach ($definition->getMethodCalls() as $call)
    {
      $arguments = array();
      foreach ($call[1] as $value)
      {
        $arguments[] = $this->dumpValue($value);
      }

      $calls .= sprintf("    \$instance->%s(%s);\n", $call[0], implode(', ', $arguments));
    }

    return $calls;
  }

  protected function addServiceConfigurator($id, $definition)
  {
    if (!$callable = $definition->getConfigurator())
    {
      return '';
    }

    if (is_array($callable))
    {
      if (is_object($callable[0]) && $callable[0] instanceof cvServiceReference)
      {
        return sprintf("    %s->%s(\$instance);\n", $this->getServiceCall($callable[0]), $callable[1]);
      }
      else
      {
        return sprintf("    call_user_func(array(%s, '%s'), \$instance);\n", $this->dumpValue($callable[0]), $callable[1]);
      }
    }
    else
    {
      return sprintf("    %s(\$instance);\n", $callable);
    }
  }

  protected function addService($id, $definition)
  {
    $name = cvServiceContainer::camelize($id);

    $code = <<<EOF

  public function get{$name}Service()
  {

EOF;

    $code .=
      $this->addServiceInclude($id, $definition).
      $this->addServiceShared($id, $definition).
      $this->addServiceInstance($id, $definition).
      $this->addServiceMethodCalls($id, $definition).
      $this->addServiceConfigurator($id, $definition).
      $this->addServiceReturn($id, $definition)
    ;

    return $code;
  }

  protected function addServiceAlias($alias, $id)
  {
    $name = cvServiceContainer::camelize($alias);
    $reference = new cvServiceReference($id['service'], ($id['container']!==false)?$id['container']:null);

    return <<<EOF

  public function get{$name}Service()
  {
    return {$this->getServiceCall($reference)};
  }

EOF;
  }

  protected function addServices()
  {
    $code = '';
    foreach ($this->container->getServiceDefinitions() as $id => $definition)
    {
      $code .= $this->addService($id, $definition);
    }

    foreach ($this->container->getAliases() as $alias => $id)
    {
      $code .= $this->addServiceAlias($alias, $id);
    }

    return $code;
  }

  protected function startClass($class, $baseClass)
  {
    return <<<EOF
<?php

class $class extends $baseClass
{
  protected \$shared = array();

EOF;
  }

  protected function addConstructor()
  {
    if (!$this->container->getParameters())
    {
      return <<<EOF

  public function __construct(cvServiceContainer \$sc)
  {
    \$this->setBasicContainer(\$sc);
  }

EOF;
    }

    return <<<EOF

  public function __construct(cvServiceContainer \$sc)
  {
    \$this->setBasicContainer(\$sc);
    \$this->basic_container->addParameters(\$this->getDefaultParameters());
  }

EOF;
  }

  protected function addDefaultParametersMethod()
  {
    if (!$this->container->getParameters())
    {
      return '';
    }

    $parameters = $this->exportParameters($this->container->getParameters());

    return <<<EOF

  protected function getDefaultParameters()
  {
    return $parameters;
  }

EOF;
  }

  protected function exportParameters($parameters, $indent = 6)
  {
    $php = array();
    foreach ($parameters as $key => $value)
    {
      if (is_array($value))
      {
        $value = $this->exportParameters($value, $indent + 2);
      }
      elseif ($value instanceof cvServiceReference)
      {
        if (!is_null($value->getIdContainer())) {
            $value = sprintf("new cvServiceReference('%s', '%s')", $value->getIdService(), $value->getIdContainer());
        } else {
            $value = sprintf("new cvServiceReference('%s')", $value->getIdService());
        }
      }
      elseif ($value instanceof cvServiceParameter)
      {
        $value = sprintf("\$this->basic_container->getParameter('%s')", $value);
      }
      else
      {
        $value = var_export($value, true);
      }

      $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value);
    }

    return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 2));
  }

  protected function endClass()
  {
    return <<<EOF
}

EOF;
  }

  protected function dumpValue($value)
  {
    if (is_array($value))
    {
      $code = array();
      foreach ($value as $k => $v)
      {
        $code[] = sprintf("%s => %s", $this->dumpValue($k), $this->dumpValue($v));
      }

      return sprintf("array(%s)", implode(', ', $code));
    }
    elseif (is_object($value) && $value instanceof cvServiceReference)
    {
      return $this->getServiceCall($value);
    }
    elseif (is_object($value) && $value instanceof cvServiceParameter)
    {
      return sprintf("\$this->basic_container->getParameter('%s')", strtolower($value));
    }
    elseif (is_string($value))
    {
      if (preg_match('/^%([^%]+)%$/', $value, $match))
      {
        // we do this to deal with non string values (boolean, integer, ...)
        // the preg_replace_callback converts them to strings
        return sprintf("\$this->basic_container->getParameter('%s')", strtolower($match[1]));
      }
      else
      {
        $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', array($this, 'replaceParameter'), var_export($value, true)));

        // optimize string
        $code = preg_replace(array("/^''\./", "/\.''$/", "/\.''\./"), array('', '', '.'), $code);

        return $code;
      }
    }
    elseif (is_object($value) || is_resource($value))
    {
      throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
    }
    else
    {
      return var_export($value, true);
    }
  }

  public function replaceParameter($match)
  {
    return sprintf("'.\$this->basic_container->getParameter('%s').'", strtolower($match[2]));
  }

  protected function getServiceCall(cvServiceReference $service_reference)
  {
    if ('service_container' == $service_reference->getIdService() && is_null($service_reference->getIdContainer()))
    {
      return '$this->basic_container';
    } elseif (!is_null($service_reference->getIdContainer())) {
        return sprintf('$this->basic_container->getService(\'%s\', \'%s\')', $service_reference->getIdService(), $service_reference->getIdContainer());
    }

    return sprintf('$this->basic_container->getService(\'%s\')', $service_reference->getIdService());
  }
}

?>