Skip to content

Commit

Permalink
Merge 379b4f3 into fbf89a0
Browse files Browse the repository at this point in the history
  • Loading branch information
mnapoli committed Jun 18, 2017
2 parents fbf89a0 + 379b4f3 commit 6d42936
Show file tree
Hide file tree
Showing 12 changed files with 71 additions and 71 deletions.
4 changes: 2 additions & 2 deletions doc/container-configuration.md
Expand Up @@ -28,8 +28,8 @@ In production environment, you will of course favor speed:

```php
$builder = new \DI\ContainerBuilder();
$builder->compile('tmp/CompiledContainer.php');
$builder->writeProxiesToFile(true, 'tmp/proxies');
$builder->enableCompilation(__DIR__ . '/tmp');
$builder->writeProxiesToFile(true, __DIR__ . '/tmp/proxies');

$container = $builder->build();
```
Expand Down
15 changes: 5 additions & 10 deletions doc/performances.md
Expand Up @@ -20,29 +20,24 @@ In order to avoid those two tasks, the container can be compiled into PHP code o

### Setup

Compiling the container is as easy as calling the `compile()` method on the container builder:
Compiling the container is as easy as calling the `enableCompilation()` method on the container builder:

```php
$containerBuilder = new \DI\ContainerBuilder();
$containerBuilder->compile(__DIR__ . '/var/cache/CompiledContainer.php');
$containerBuilder->enableCompilation(__DIR__ . '/var/cache');

// […]

$container = $containerBuilder->build();
```

The `compile()` method takes a single argument: the name of a file in which to store the container.

Please note that the file name will also be the name of the generated PHP class. Because of that the filename you specify must also be a valid class name. For example:

- `var/cache/CompiledContainer.php`: valid
- `var/cache/compiled-container.php`: invalid since `compiled-container` is not a valid PHP class name
The `enableCompilation()` method takes the name of the directory in which to store the compiled container.

### Deployment in production

When a container is configured to be compiled, **it will be compiled once and never be regenerated again**. That allows for maximum performances in production.

When you deploy new versions of your code to production **you must delete the generated file** to ensure that the container is re-compiled.
When you deploy new versions of your code to production **you must delete the generated file** (or the directory that contains it) to ensure that the container is re-compiled.

If your production handles a lot of traffic you may also want to generate the compiled container *before* the new version of your code goes live. That phase is known as the "warmup" phase. To do this, simply create the container (call `$containerBuilder->build()`) during your deployment step and the compiled container will be created.

Expand All @@ -53,7 +48,7 @@ If your production handles a lot of traffic you may also want to generate the co
```php
$containerBuilder = new \DI\ContainerBuilder();
if (/* is production */) {
$containerBuilder->compile(__DIR__ . '/var/cache/CompiledContainer.php');
$containerBuilder->enableCompilation(__DIR__ . '/var/cache');
}
```

Expand Down
18 changes: 7 additions & 11 deletions src/Compiler.php
Expand Up @@ -46,21 +46,15 @@ class Compiler
/**
* Compile the container.
*
* @return string The compiled container class name.
* @return string The compiled container file name.
*/
public function compile(DefinitionSource $definitionSource, string $fileName) : string
public function compile(DefinitionSource $definitionSource, string $directory, string $className) : string
{
$this->containerClass = basename($fileName, '.php');

// Validate that it's a valid class name
$validClassName = preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $this->containerClass);
if (!$validClassName) {
throw new InvalidArgumentException("The file in which to compile the container must have a name that is a valid class name: {$this->containerClass} is not a valid PHP class name");
}
$fileName = $directory . '/' . $className . '.php';

if (file_exists($fileName)) {
// The container is already compiled
return $this->containerClass;
return $fileName;
}

$definitions = $definitionSource->getDefinitions();
Expand All @@ -74,6 +68,8 @@ public function compile(DefinitionSource $definitionSource, string $fileName) :
$this->compileDefinition($entryName, $definition);
}

$this->containerClass = $className;

ob_start();
require __DIR__ . '/Compiler/Template.php';
$fileContent = ob_get_contents();
Expand All @@ -84,7 +80,7 @@ public function compile(DefinitionSource $definitionSource, string $fileName) :
$this->createCompilationDirectory(dirname($fileName));
file_put_contents($fileName, $fileContent);

return $this->containerClass;
return $fileName;
}

/**
Expand Down
29 changes: 16 additions & 13 deletions src/ContainerBuilder.php
Expand Up @@ -85,7 +85,7 @@ class ContainerBuilder
/**
* @var string|null
*/
private $compileToFile;
private $compileToDirectory;

/**
* Build a container configured for the dev environment.
Expand Down Expand Up @@ -143,12 +143,13 @@ public function build()

$containerClass = $this->containerClass;

if ($this->compileToFile) {
$containerClass = (new Compiler)->compile($source, $this->compileToFile);
if ($this->compileToDirectory) {
$compiler = new Compiler;
$compiledContainerFile = $compiler->compile($source, $this->compileToDirectory, $containerClass);
// Only load the file if it hasn't been already loaded
// (the container can be created multiple times in the same process)
if (!class_exists($containerClass, false)) {
require $this->compileToFile;
require $compiledContainerFile;
}
}

Expand All @@ -158,25 +159,27 @@ public function build()
/**
* Compile the container for optimum performances.
*
* The filename provided must be a valid class name! For example:
*
* - `var/cache/ContainerProd.php` -> valid since `ContainerProd` is a valid class name
* - `var/cache/Container-Prod.php` -> invalid since `Container-Prod` is NOT a valid class name
*
* Be aware that the container is compiled once and never updated!
*
* Therefore:
*
* - in production you should clear that directory every time you deploy
* - in development you should not compile the container
*
* @param string $fileName File in which to put the compiled container.
* If you provide a filename it must be a valid class name! For example:
*
* - `ContainerProd.php` -> valid since `ContainerProd` is a valid class name
* - `Container-Prod.php` -> invalid since `Container-Prod` is NOT a valid class name
*
* @param string $directory Directory in which to put the compiled container.
* @param string $className Name of the class. Customize only if necessary.
*/
public function compile(string $fileName) : ContainerBuilder
public function enableCompilation(string $directory, string $className = 'CompiledContainer') : ContainerBuilder
{
$this->ensureNotLocked();

$this->compileToFile = $fileName;
$this->compileToDirectory = $directory;
$this->containerClass = $className;

return $this;
}
Expand Down Expand Up @@ -299,7 +302,7 @@ public function addDefinitions($definitions) : ContainerBuilder
*/
public function isCompiled() : bool
{
return (bool) $this->compileToFile;
return (bool) $this->compileToDirectory;
}

private function ensureNotLocked()
Expand Down
17 changes: 10 additions & 7 deletions tests/IntegrationTest/BaseContainerTest.php
Expand Up @@ -12,41 +12,44 @@
*/
abstract class BaseContainerTest extends TestCase
{
const COMPILED_CONTAINER_DIRECTORY = __DIR__ . '/tmp';
const COMPILATION_DIR = __DIR__ . '/tmp';

public static function setUpBeforeClass()
{
// Clear all files
array_map('unlink', glob(self::COMPILED_CONTAINER_DIRECTORY . '/*'));
array_map('unlink', glob(self::COMPILATION_DIR . '/*'));

parent::setUpBeforeClass();
}

public function setUp()
{
// Clear all files
array_map('unlink', glob(self::COMPILED_CONTAINER_DIRECTORY . '/*'));
array_map('unlink', glob(self::COMPILATION_DIR . '/*'));

parent::setUp();
}

public function provideContainer() : array
{
// Clear all files
array_map('unlink', glob(self::COMPILED_CONTAINER_DIRECTORY . '/*'));
array_map('unlink', glob(self::COMPILATION_DIR . '/*'));

return [
'not-compiled' => [
new ContainerBuilder,
],
'compiled' => [
(new ContainerBuilder)->compile(self::generateCompilationFileName()),
(new ContainerBuilder)->enableCompilation(
self::COMPILATION_DIR,
self::generateCompiledClassName()
),
],
];
}

protected static function generateCompilationFileName()
protected static function generateCompiledClassName()
{
return self::COMPILED_CONTAINER_DIRECTORY . '/Container' . uniqid() . '.php';
return 'Container' . uniqid();
}
}
30 changes: 9 additions & 21 deletions tests/IntegrationTest/CompiledContainerTest.php
Expand Up @@ -17,7 +17,7 @@ class CompiledContainerTest extends BaseContainerTest
public function the_same_container_can_be_recreated_multiple_times()
{
$builder = new ContainerBuilder;
$builder->compile(self::generateCompilationFileName());
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->addDefinitions([
'foo' => 'bar',
]);
Expand All @@ -30,22 +30,22 @@ public function the_same_container_can_be_recreated_multiple_times()
/** @test */
public function the_container_is_compiled_once_and_never_recompiled_after()
{
$compiledContainerFile = self::generateCompilationFileName();
$compiledContainerClass = self::generateCompiledClassName();

// Create a first compiled container in the file
$builder = new ContainerBuilder;
$builder->addDefinitions([
'foo' => 'bar',
]);
$builder->compile($compiledContainerFile);
$builder->enableCompilation(self::COMPILATION_DIR, $compiledContainerClass);
$builder->build();

// Create a second compiled container in the same file but with a DIFFERENT configuration
$builder = new ContainerBuilder;
$builder->addDefinitions([
'foo' => 'DIFFERENT',
]);
$builder->compile($compiledContainerFile);
$builder->enableCompilation(self::COMPILATION_DIR, $compiledContainerClass);
$container = $builder->build();

// The second container is actually using the config of the first because the container was already compiled
Expand All @@ -68,7 +68,7 @@ public function anonymous_classes_cannot_be_compiled()
$builder->addDefinitions([
'foo' => create($class),
]);
$builder->compile(self::generateCompilationFileName());
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->build();
}

Expand All @@ -86,7 +86,7 @@ public function factories_nested_in_other_definitions_cannot_be_compiled()
return 'hello';
})),
]);
$builder->compile(self::generateCompilationFileName());
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->build();
}

Expand All @@ -102,7 +102,7 @@ public function object_nested_in_other_definitions_cannot_be_compiled()
\stdClass::class => create()
->property('foo', new \stdClass),
]);
$builder->compile(self::generateCompilationFileName());
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->build();
}

Expand All @@ -123,19 +123,7 @@ public function object_nested_in_arrays_cannot_be_compiled()
],
],
]);
$builder->compile(self::generateCompilationFileName());
$builder->build();
}

/**
* @test
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The file in which to compile the container must have a name that is a valid class name: foo-bar is not a valid PHP class name
*/
public function the_compiled_filename_must_be_a_valid_class_name()
{
$builder = new ContainerBuilder;
$builder->compile('/tmp/foo-bar.php');
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->build();
}

Expand All @@ -147,7 +135,7 @@ public function the_compiled_filename_must_be_a_valid_class_name()
public function entries_cannot_be_overridden_by_definitions_in_the_compiled_container()
{
$builder = new ContainerBuilder;
$builder->compile(self::generateCompilationFileName());
$builder->enableCompilation(self::COMPILATION_DIR, self::generateCompiledClassName());
$builder->addDefinitions([
'foo' => create(\stdClass::class),
]);
Expand Down
2 changes: 1 addition & 1 deletion tests/PerformanceTest/call.php
Expand Up @@ -8,7 +8,7 @@
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$builder->useAnnotations(false);
$builder->compile(__DIR__ . '/tmp/call.php');
$builder->enableCompilation(__DIR__ . '/tmp', 'Call');
$builder->addDefinitions([
'link' => 'Hello',
]);
Expand Down
2 changes: 1 addition & 1 deletion tests/PerformanceTest/factory.php
Expand Up @@ -16,7 +16,7 @@ class Bar
$builder = new ContainerBuilder();
$builder->useAutowiring(false);
$builder->useAnnotations(false);
$builder->compile(__DIR__ . '/tmp/factory.php');
$builder->enableCompilation(__DIR__ . '/tmp', 'Factory');
$builder->addDefinitions(__DIR__ . '/factory/config.php');

$container = $builder->build();
Expand Down
2 changes: 1 addition & 1 deletion tests/PerformanceTest/get-cache.php
Expand Up @@ -22,7 +22,7 @@
$builder->useAnnotations(false);
$builder->addDefinitions(__DIR__ . '/get/config.php');
if ($compile) {
$builder->compile(__DIR__ . "/tmp/container$i.php");
$builder->enableCompilation(__DIR__ . '/tmp/', "Container$i");
}
$container = $builder->build();

Expand Down
2 changes: 1 addition & 1 deletion tests/PerformanceTest/get-object.php
Expand Up @@ -10,7 +10,7 @@
$builder->useAutowiring(true);
$builder->useAnnotations(false);
$builder->addDefinitions(__DIR__ . '/get-object/config.php');
$builder->compile(__DIR__ . '/tmp/getobject.php');
$builder->enableCompilation(__DIR__ . '/tmp', 'GetObject');
$container = $builder->build();

$container->get('object1');
Expand Down
2 changes: 1 addition & 1 deletion tests/PerformanceTest/get.php
Expand Up @@ -12,7 +12,7 @@
$builder->useAutowiring(true);
$builder->useAnnotations(false);
$builder->addDefinitions(__DIR__ . '/get/config.php');
$builder->compile(__DIR__ . '/tmp/get.php');
$builder->enableCompilation(__DIR__ . '/tmp', 'Get');
$container = $builder->build();

$container->get(A::class);
Expand Down

0 comments on commit 6d42936

Please sign in to comment.