-
-
Notifications
You must be signed in to change notification settings - Fork 312
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
66 changed files
with
2,155 additions
and
1,594 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?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->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) { | ||
return $this->resolvedEntries[$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->resolveCompiledEntry($name, $method); | ||
|
||
// Store the entry to always return it without recomputing it | ||
$this->resolvedEntries[$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); | ||
} | ||
|
||
/** | ||
* Resolves a compiled entry. | ||
* | ||
* Checks for circular dependencies while resolving it. | ||
* | ||
* @throws DependencyException Error while resolving the entry. | ||
* @return mixed | ||
*/ | ||
private function resolveCompiledEntry(string $entryName, string $method) | ||
{ | ||
// Check if we are already getting this entry -> circular dependency | ||
if (isset($this->entriesBeingResolved[$entryName])) { | ||
throw new DependencyException("Circular dependency detected while trying to resolve entry '$entryName'"); | ||
} | ||
$this->entriesBeingResolved[$entryName] = true; | ||
|
||
try { | ||
$value = $this->$method(); | ||
} finally { | ||
unset($this->entriesBeingResolved[$entryName]); | ||
} | ||
|
||
return $value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
<?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(); | ||
$code = 'return $this->delegateContainer->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->delegateContainer);'; | ||
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) { | ||
$value = $value->getDefinition(''); | ||
} | ||
|
||
if ($value instanceof Definition) { | ||
// Give it an arbitrary unique name | ||
$subEntryName = uniqid('SubEntry'); | ||
// 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)); | ||
} | ||
} | ||
} |
Oops, something went wrong.