Skip to content

Commit

Permalink
Implement more robust caching system (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
cspray authored Jun 5, 2024
1 parent 7fd9a27 commit 9a9601e
Show file tree
Hide file tree
Showing 43 changed files with 913 additions and 686 deletions.
5 changes: 4 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ static-analysis:

# Set the baseline of known issues to be used during static analysis
static-analysis-set-baseline:
@./tools/psalm/vendor/bin/psalm --set-baseline=known-issues.xml
@./tools/psalm/vendor/bin/psalm --set-baseline=known-issues.xml --no-cache

# Update the baseline to _remove_ fixed issues. If new issues are to be added please use static-analysis-set-baseline
static-analysis-update-baseline:
@./tools/psalm/vendor/bin/psalm --update-baseline --no-cache

static-analysis-clear-cache:
@./tools/psalm/vendor/bin/psalm --clear-cache

# Run code-linting tools on src and test
code-lint:
@./tools/labrador-cs/vendor/bin/phpcs --version
Expand Down
1 change: 0 additions & 1 deletion annotated-container.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
</xs:annotation>
</xs:element>

<xs:element name="cacheDir" type="xs:token" minOccurs="0" maxOccurs="1"/>
<xs:element name="definitionProviders" type="definitionProvidersType" minOccurs="0" maxOccurs="1" />
<xs:element name="parameterStores" type="parameterStoresType" minOccurs="0" maxOccurs="1" />
</xs:all>
Expand Down
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"cspray/precision-stopwatch": "^0.2.0",
"cspray/typiphy": "^0.3",
"nikic/php-parser": "^5",
"ocramius/package-versions": "^2.7",
"psr/container": "^2.0"
},
"require-dev": {
Expand Down
37 changes: 33 additions & 4 deletions docs/how-to/02-bootstrap-your-container.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ If successful you'll get a configuration file named `annotated-container.xml` in
<dir>tests</dir>
</source>
</scanDirectories>
<cacheDir>.annotated-container-cache</cacheDir>
</annotatedContainer>
```

The most important, and the only thing that's actually required, is to define at least 1 source directory to scan. By default, we also include a directory that stores a cached ContainerDefinition. Though the cache directory is not strictly required it is a good practice to include it. Caching drastically increases the performance of creating your Container. It is also required if you want to use the `./vendor/bin/annotated-container build` command to generate a ContainerDefinition ahead-of-time, for example in production.
The most important, and the only thing that's actually required, is to define at least 1 source directory to scan. It should be noted that **all** directories autoloaded in your `composer.json` will be scanned, including any `autoload-dev` entries. If this is not desired, be sure to remove these directories after the configuration is generated.

The rest of this guide will add new elements to this configuration. The steps below are optional, if you don't require any "bells & whistles" skip to Step 4.

Expand Down Expand Up @@ -63,7 +62,6 @@ Now, upgrade the configuration to let bootstrapping know which class to use.
<dir>tests</dir>
</source>
</scanDirectories>
<cacheDir>.annotated-container-cache</cacheDir>
<definitionProviders>
<definitionProvider>Acme\Demo\ThirdPartyServicesProvider</definitionProvider>
</definitionProviders>
Expand Down Expand Up @@ -109,7 +107,6 @@ Next, update your configuration.
<dir>tests</dir>
</source>
</scanDirectories>
<cacheDir>.annotated-container-cache</cacheDir>
<parameterStores>
<parameterStore>Acme\Demo\MyCustomParameterStore</parameterStore>
</parameterStores>
Expand Down Expand Up @@ -243,3 +240,35 @@ $container = Bootstrap::fromCompleteSetup(
new DefaultDefinitionProviderFactory()
))->bootstrapContainer();
```

#### Caching ContainerDefinition

The static analysis portion of Annotated Container can, like most static analysis tools, be relatively time-consuming. In PHP applications that act as long-running processes, the type this maintainer tends to develop using Annotated Container, this cost is negligible. It happens just 1 time and is just a small part of the initial startup costs. However, in traditional PHP applications that only live for the length of the request this can be costly. In this situation, it is recommended you configure your bootstrap to cache the ContainerDefinition.

Setting up caching is something that you must explicitly opt into during your bootstrapping. In the 2.x series it was possible to configure a directory to use as a cache. This was removed in 3.0 in favor of a much more robust caching mechanism. The below snippet of code is how to effectively setup your 3.0 Annotated Container to cache similarly to 2.0.

```php
<?php declare(strict_types=1);

namespace Acme\Demo;

use Cspray\AnnotatedContainer\Bootstrap\Bootstrap;
use Cspray\AnnotatedContainer\Bootstrap\CacheAwareBootstrappingConfigurationProvider;
use Cspray\AnnotatedContainer\Bootstrap\DefaultDefinitionProviderFactory;
use Cspray\AnnotatedContainer\Bootstrap\DefaultParameterStoreFactory;
use Cspray\AnnotatedContainer\Bootstrap\XmlBootstrappingConfigurationProvider;use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory;
use Cspray\AnnotatedContainer\Definition\Cache\FileBackedContainerDefinitionCache;use Cspray\AnnotatedContainer\Definition\Serializer\XmlContainerDefinitionSerializer;use Cspray\AnnotatedContainer\Event\Emitter;

$container = Bootstrap::fromMinimalSetup(new Emitter())
->bootstrapContainer(
bootstrappingConfigurationProvider: new CacheAwareBootstrappingConfigurationProvider(
new XmlBootstrappingConfigurationProvider(),
new FileBackedContainerDefinitionCache(
new XmlContainerDefinitionSerializer(),
__DIR__ . '/.annotated-container-cache'
)
)
);
```

If the cache implementations provided by Annotated Container are not sufficient, you can create your own `Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache` appropriate for your use case.
96 changes: 64 additions & 32 deletions known-issues.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.24.0@462c80e31c34e58cc4f750c656be3927e80e550e">
<file src="src/AnnotatedContainerVersion.php">
<InvalidNullableReturnType>
<code><![CDATA[string]]></code>
</InvalidNullableReturnType>
<NullableReturnStatement>
<code><![CDATA[InstalledVersions::getVersion('cspray/annotated-container')]]></code>
</NullableReturnStatement>
</file>
<file src="src/Bootstrap/ComposerJsonScanningThirdPartyInitializerProvider.php">
<MixedArrayAccess>
<code><![CDATA[$composerData['extra']]]></code>
Expand Down Expand Up @@ -52,6 +60,12 @@
</MixedReturnTypeCoercion>
</file>
<file src="src/Bootstrap/XmlBootstrappingConfiguration.php">
<InvalidReturnStatement>
<code><![CDATA[$this->cacheDir]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[?ContainerDefinitionCache]]></code>
</InvalidReturnType>
<MixedAssignment>
<code><![CDATA[$scanDirectories[]]]></code>
</MixedAssignment>
Expand All @@ -63,14 +77,26 @@
</UndefinedPropertyFetch>
</file>
<file src="src/Cli/Command/BuildCommand.php">
<InvalidArgument>
<code><![CDATA[$cacheDir]]></code>
<code><![CDATA[new XmlContainerDefinitionSerializer()]]></code>
</InvalidArgument>
<MixedArrayAccess>
<code><![CDATA[$composer['extra']['annotatedContainer']['configFile']]]></code>
</MixedArrayAccess>
<MixedAssignment>
<code><![CDATA[$configName]]></code>
</MixedAssignment>
<TooManyArguments>
<code><![CDATA[new CacheAwareContainerDefinitionAnalyzer($compiler, new XmlContainerDefinitionSerializer(), $cacheDir)]]></code>
</TooManyArguments>
</file>
<file src="src/Cli/Command/CacheClearCommand.php">
<InvalidArgument>
<code><![CDATA[$cacheDir]]></code>
<code><![CDATA[$cacheDir]]></code>
<code><![CDATA[$cacheDir]]></code>
</InvalidArgument>
<MixedArgument>
<code><![CDATA[$configName]]></code>
</MixedArgument>
Expand Down Expand Up @@ -314,6 +340,11 @@
<code><![CDATA[$concreteType]]></code>
</PropertyNotSetInConstructor>
</file>
<file src="src/Definition/Cache/FileBackedContainerDefinitionCache.php">
<ArgumentTypeCoercion>
<code><![CDATA[file_get_contents($filePath)]]></code>
</ArgumentTypeCoercion>
</file>
<file src="src/Definition/InjectDefinitionBuilder.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->methodName]]></code>
Expand All @@ -338,6 +369,39 @@
<code><![CDATA[$definition->profiles()]]></code>
</InvalidArgument>
</file>
<file src="src/Definition/Serializer/XmlContainerDefinitionSerializer.php">
<ArgumentTypeCoercion>
<code><![CDATA[$dom->saveXML()]]></code>
<code><![CDATA[$name]]></code>
</ArgumentTypeCoercion>
<InvalidArgument>
<code><![CDATA[$serviceProfiles]]></code>
</InvalidArgument>
<InvalidScalarArgument>
<code><![CDATA[$injectProfiles]]></code>
</InvalidScalarArgument>
<MixedArgument>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
</MixedArgument>
<MixedAssignment>
<code><![CDATA[$isPrimary]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<PossiblyNullArgument>
<code><![CDATA[$injectProfiles]]></code>
</PossiblyNullArgument>
<RedundantCondition>
<code><![CDATA[$methodName !== null]]></code>
<code><![CDATA[assert($methodName !== null)]]></code>
</RedundantCondition>
<UndefinedPropertyFetch>
<code><![CDATA[$xpath->query('@isPrimary', $serviceDefinition)[0]?->value]]></code>
<code><![CDATA[$xpath->query('@isPrimary', $serviceDefinition)[0]?->value]]></code>
</UndefinedPropertyFetch>
</file>
<file src="src/Definition/ServiceDefinitionBuilder.php">
<PropertyNotSetInConstructor>
<code><![CDATA[$isAbstract]]></code>
Expand Down Expand Up @@ -377,38 +441,6 @@
<code><![CDATA[$types]]></code>
</InvalidScalarArgument>
</file>
<file src="src/Serializer/ContainerDefinitionSerializer.php">
<ArgumentTypeCoercion>
<code><![CDATA[$name]]></code>
</ArgumentTypeCoercion>
<InvalidArgument>
<code><![CDATA[$serviceProfiles]]></code>
</InvalidArgument>
<InvalidScalarArgument>
<code><![CDATA[$injectProfiles]]></code>
</InvalidScalarArgument>
<MixedArgument>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
<code><![CDATA[unserialize(base64_decode($attr))]]></code>
</MixedArgument>
<MixedAssignment>
<code><![CDATA[$isPrimary]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<PossiblyNullArgument>
<code><![CDATA[$injectProfiles]]></code>
</PossiblyNullArgument>
<RedundantCondition>
<code><![CDATA[$methodName !== null]]></code>
<code><![CDATA[assert($methodName !== null)]]></code>
</RedundantCondition>
<UndefinedPropertyFetch>
<code><![CDATA[$xpath->query('@isPrimary', $serviceDefinition)[0]?->value]]></code>
<code><![CDATA[$xpath->query('@isPrimary', $serviceDefinition)[0]?->value]]></code>
</UndefinedPropertyFetch>
</file>
<file src="src/StaticAnalysis/AnnotatedTargetDefinitionConverter.php">
<ArgumentTypeCoercion>
<code><![CDATA[$types]]></code>
Expand Down
4 changes: 2 additions & 2 deletions src/AnnotatedContainerVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

namespace Cspray\AnnotatedContainer;

use PackageVersions\Versions;
use Composer\InstalledVersions;

final class AnnotatedContainerVersion {

private function __construct() {
}

public static function version() : string {
return Versions::getVersion('cspray/annotated-container');
return InstalledVersions::getVersion('cspray/annotated-container');
}
}
31 changes: 13 additions & 18 deletions src/Bootstrap/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@

namespace Cspray\AnnotatedContainer\Bootstrap;

use Auryn\Injector as AurynContainer;
use Cspray\AnnotatedContainer\AnnotatedContainer;
use Cspray\AnnotatedContainer\ContainerFactory\AurynContainerFactory;
use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory;
use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder;
use Cspray\AnnotatedContainer\ContainerFactory\IlluminateContainerFactory;
use Cspray\AnnotatedContainer\ContainerFactory\PhpDiContainerFactory;
use Cspray\AnnotatedContainer\Definition\ContainerDefinition;
use Cspray\AnnotatedContainer\Definition\Serializer\XmlContainerDefinitionSerializer;
use Cspray\AnnotatedContainer\Event\ContainerFactoryEmitter;
use Cspray\AnnotatedContainer\Event\Emitter;
use Cspray\AnnotatedContainer\Profiles;
use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetContainerDefinitionAnalyzer;
use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetDefinitionConverter;
use Cspray\AnnotatedContainer\StaticAnalysis\CacheAwareContainerDefinitionAnalyzer;
use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptions;
use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalysisOptionsBuilder;
use Cspray\AnnotatedContainer\StaticAnalysis\ContainerDefinitionAnalyzer;
use Cspray\AnnotatedContainer\StaticAnalysis\AnnotatedTargetDefinitionConverter;
use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactory;
use Cspray\AnnotatedContainer\ContainerFactory\ContainerFactoryOptionsBuilder;
use Cspray\AnnotatedContainer\Serializer\ContainerDefinitionSerializer;
use Cspray\AnnotatedTarget\PhpParserAnnotatedTargetParser;
use Cspray\PrecisionStopwatch\Marker;
use Cspray\PrecisionStopwatch\Metrics;
use Cspray\PrecisionStopwatch\Stopwatch;
use DI\Container as PhpDiContainer;
use Illuminate\Container\Container as IlluminateContainer;
use Auryn\Injector as AurynContainer;
use RuntimeException;

final class Bootstrap {
Expand Down Expand Up @@ -154,25 +154,20 @@ private function runStaticAnalysis(
BootstrappingConfiguration $configuration,
ContainerDefinitionAnalysisOptions $analysisOptions
) : ContainerDefinition {
$cacheDir = null;
$configuredCacheDir = $configuration->cacheDirectory();
if ($configuredCacheDir !== null) {
$cacheDir = $this->directoryResolver->cachePath($configuredCacheDir);
}
return $this->containerDefinitionAnalyzer($cacheDir)->analyze($analysisOptions);
}

private function containerDefinitionAnalyzer(?string $cacheDir) : ContainerDefinitionAnalyzer {
$compiler = new AnnotatedTargetContainerDefinitionAnalyzer(
$analyzer = new AnnotatedTargetContainerDefinitionAnalyzer(
new PhpParserAnnotatedTargetParser(),
new AnnotatedTargetDefinitionConverter(),
$this->emitter
);
if ($cacheDir !== null) {
$compiler = new CacheAwareContainerDefinitionAnalyzer($compiler, new ContainerDefinitionSerializer(), $cacheDir);
$cache = $configuration->cache();
if ($cache !== null) {
$analyzer = new CacheAwareContainerDefinitionAnalyzer(
$analyzer,
$cache
);
}

return $compiler;
return $analyzer->analyze($analysisOptions);
}

private function createContainer(
Expand Down
3 changes: 2 additions & 1 deletion src/Bootstrap/BootstrappingConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Cspray\AnnotatedContainer\Bootstrap;

use Cspray\AnnotatedContainer\ArchitecturalDecisionRecords\SingleEntrypointDefinitionProvider;
use Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache;
use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider;
use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore;

Expand All @@ -13,7 +14,7 @@ interface BootstrappingConfiguration {
*/
public function scanDirectories() : array;

public function cacheDirectory() : ?string;
public function cache() : ?ContainerDefinitionCache;

#[SingleEntrypointDefinitionProvider]
public function containerDefinitionProvider() : ?DefinitionProvider;
Expand Down
33 changes: 33 additions & 0 deletions src/Bootstrap/CacheAwareBootstrappingConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace Cspray\AnnotatedContainer\Bootstrap;

use Cspray\AnnotatedContainer\ArchitecturalDecisionRecords\SingleEntrypointDefinitionProvider;
use Cspray\AnnotatedContainer\Definition\Cache\ContainerDefinitionCache;
use Cspray\AnnotatedContainer\StaticAnalysis\DefinitionProvider;

final class CacheAwareBootstrappingConfiguration implements BootstrappingConfiguration {

public function __construct(
private readonly BootstrappingConfiguration $configuration,
private readonly ContainerDefinitionCache $cache,
) {
}

public function scanDirectories() : array {
return $this->configuration->scanDirectories();
}

public function cache() : ?ContainerDefinitionCache {
return $this->cache;
}

#[SingleEntrypointDefinitionProvider]
public function containerDefinitionProvider() : ?DefinitionProvider {
return $this->configuration->containerDefinitionProvider();
}

public function parameterStores() : array {
return $this->configuration->parameterStores();
}
}
Loading

0 comments on commit 9a9601e

Please sign in to comment.