Skip to content

Commit

Permalink
Merge e9a1ddf into 00dee13
Browse files Browse the repository at this point in the history
  • Loading branch information
mnapoli committed May 31, 2017
2 parents 00dee13 + e9a1ddf commit aba7f85
Show file tree
Hide file tree
Showing 58 changed files with 1,227 additions and 1,045 deletions.
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@
"require": {
"php": ">=7.0.0",
"psr/container": "^1.0",
"psr/simple-cache": "^1.0",
"php-di/invoker": "^2.0",
"php-di/phpdoc-reader": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "~5.0",
"phpunit/phpunit": "~5.7",
"mnapoli/phpunit-easymock": "~0.2.0",
"doctrine/annotations": "~1.2",
"ocramius/proxy-manager": "~2.0.2"
Expand Down
4 changes: 3 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
syntaxCheck="true"
forceCoversAnnotation="true"
bootstrap="./vendor/autoload.php">

<testsuites>
Expand All @@ -24,6 +23,9 @@
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
<exclude>
<file>src/DI/Compiler/Template.php</file>
</exclude>
</whitelist>
</filter>

Expand Down
91 changes: 0 additions & 91 deletions src/DI/Cache/ArrayCache.php

This file was deleted.

56 changes: 56 additions & 0 deletions src/DI/CompiledContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace DI;

/**
* Compiled version of the dependency injection container.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
abstract class CompiledContainer extends Container
{
/**
* {@inheritdoc}
*/
public function get($name)
{
// Try to find the entry in the singleton map
if (isset($this->singletonEntries[$name]) || array_key_exists($name, $this->singletonEntries)) {
return $this->singletonEntries[$name];
}

$method = static::METHOD_MAPPING[$name] ?? null;

// If it's a compiled entry, then there is a method in this class
if ($method !== null) {
$value = $this->$method();

// Store the entry to always return it without recomputing it
$this->singletonEntries[$name] = $value;

return $value;
}

return parent::get($name);
}

/**
* {@inheritdoc}
*/
public function has($name)
{
if (! is_string($name)) {
throw new \InvalidArgumentException(sprintf(
'The name parameter must be of type string, %s given',
is_object($name) ? get_class($name) : gettype($name)
));
}

// The parent method is overridden to check in our array, it avoids resolving definitions
if (isset(static::METHOD_MAPPING[$name])) {
return true;
}

return parent::has($name);
}
}
167 changes: 167 additions & 0 deletions src/DI/Compiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

declare(strict_types=1);

namespace DI;

use DI\Compiler\ObjectCreationCompiler;
use DI\Definition\AliasDefinition;
use DI\Definition\ArrayDefinition;
use DI\Definition\Definition;
use DI\Definition\EnvironmentVariableDefinition;
use DI\Definition\FactoryDefinition;
use DI\Definition\Helper\DefinitionHelper;
use DI\Definition\ObjectDefinition;
use DI\Definition\Source\DefinitionSource;
use DI\Definition\StringDefinition;
use DI\Definition\ValueDefinition;
use InvalidArgumentException;

/**
* @author Matthieu Napoli <matthieu@mnapoli.fr>
*/
class Compiler
{
/**
* @var string
*/
private $containerClass;

/**
* Map of entry names to method names.
*
* @var string[]
*/
private $entryToMethodMapping = [];

/**
* @var string[]
*/
private $methods = [];

/**
* Compile the container.
*/
public function compile(DefinitionSource $definitionSource, string $fileName)
{
if (file_exists($fileName)) {
// The container is already compiled
return;
}

$definitions = $definitionSource->getDefinitions();

// The name of the class must be unique to allow using multiple compiled containers
// in the same process (for example for tests).
$this->containerClass = uniqid('CompiledContainer');

foreach ($definitions as $entryName => $definition) {
if ($definition instanceof FactoryDefinition) {
continue;
}
if ($definition instanceof ValueDefinition && is_object($definition->getValue())) {
continue;
}
$this->compileDefinition($entryName, $definition);
}

ob_start();
require __DIR__ . '/Compiler/Template.php';
$fileContent = ob_get_contents();
ob_end_clean();

$fileContent = "<?php\n" . $fileContent;

$this->createCompilationDirectory(basename($fileName));
file_put_contents($fileName, $fileContent);
}

/**
* @return string The method name
*/
private function compileDefinition(string $entryName, Definition $definition) : string
{
// Generate a unique method name
$methodName = uniqid('get');
$this->entryToMethodMapping[$entryName] = $methodName;

switch (true) {
case $definition instanceof ValueDefinition:
$value = $definition->getValue();
$code = 'return ' . $this->compileValue($value) . ';';
break;
case $definition instanceof AliasDefinition:
$targetEntryName = $definition->getTargetEntryName();
// TODO delegate container
$code = 'return $this->get(' . $this->compileValue($targetEntryName) . ');';
break;
case $definition instanceof StringDefinition:
$entryName = $this->compileValue($definition->getName());
$expression = $this->compileValue($definition->getExpression());
// TODO delegate container
$code = 'return \DI\Definition\StringDefinition::resolveExpression(' . $entryName . ', ' . $expression . ', $this);';
break;
case $definition instanceof EnvironmentVariableDefinition:
$variableName = $this->compileValue($definition->getVariableName());
$isOptional = $this->compileValue($definition->isOptional());
$defaultValue = $this->compileValue($definition->getDefaultValue());
$code = <<<PHP
\$value = getenv($variableName);
if (false !== \$value) return \$value;
if (!$isOptional) {
throw new \DI\Definition\Exception\InvalidDefinition("The environment variable '{$definition->getVariableName()}' has not been defined");
}
return $defaultValue;
PHP;
break;
case $definition instanceof ArrayDefinition:
$values = $definition->getValues();
$values = array_map(function ($value) {
return ' ' . $this->compileValue($value) . ",\n";
}, $values);
$values = implode('', $values);
$code = "return [\n$values ];";
break;
case $definition instanceof ObjectDefinition:
$compiler = new ObjectCreationCompiler($this);
$code = $compiler->compile($definition);
$code .= "\n return \$object;";
break;
default:
throw new \Exception('Cannot compile definition of type ' . get_class($definition));
}

$this->methods[$methodName] = $code;

return $methodName;
}

public function compileValue($value) : string
{
if ($value instanceof DefinitionHelper) {
// Give it an arbitrary unique name
$subEntryName = uniqid('SubEntry');
$value = $value->getDefinition($subEntryName);
// Compile the sub-definition in another method
$methodName = $this->compileDefinition($subEntryName, $value);
// The value is now a method call to that method (which returns the value)
return "\$this->$methodName()";
} elseif (is_object($value)) {
throw new \Exception('Cannot compile an object');
} elseif (is_resource($value)) {
throw new \Exception('Cannot compile a resource');
}

return var_export($value, true);
}

private function createCompilationDirectory(string $directory)
{
if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
throw new InvalidArgumentException(sprintf('Compilation directory does not exist and cannot be created: %s.', $directory));
}
if (!is_writable($directory)) {
throw new InvalidArgumentException(sprintf('Compilation directory is not writable: %s.', $directory));
}
}
}

0 comments on commit aba7f85

Please sign in to comment.