Skip to content

Commit 44b74cc

Browse files
committed
improve cache expire, add new tests
1 parent 8bffbd1 commit 44b74cc

17 files changed

+809
-417
lines changed
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace PTS\SymfonyDiLoader;
5+
6+
class CacheWatcher
7+
{
8+
/**
9+
* @param string $fileCache
10+
* @param string[] $configs
11+
*
12+
* @return bool
13+
*/
14+
public function isActualCache(string $fileCache, array $configs): bool
15+
{
16+
$oldConfigs = $this->getMetaCache($fileCache . '.meta');
17+
if (\count($oldConfigs) !== \count($configs)) {
18+
return false;
19+
}
20+
21+
$diff = array_diff($oldConfigs, $configs);
22+
if (\count($diff)) {
23+
return false;
24+
}
25+
26+
return !$this->isExpired($fileCache, $configs);
27+
}
28+
29+
/**
30+
* @param string $fileMeta
31+
*
32+
* @return string[]
33+
*/
34+
protected function getMetaCache(string $fileMeta): array
35+
{
36+
if (!file_exists($fileMeta)) {
37+
throw new \RuntimeException('Can`t read meta for DI container');
38+
}
39+
40+
$configs = file_get_contents($fileMeta);
41+
return unserialize($configs);
42+
}
43+
44+
/**
45+
* @param string $fileCache
46+
* @param string[] $configs
47+
*
48+
* @return bool
49+
*/
50+
public function isExpired(string $fileCache, array $configs): bool
51+
{
52+
$cacheTime = filemtime($fileCache);
53+
54+
foreach ($configs as $config) {
55+
if ($cacheTime < filemtime($config)) {
56+
return true;
57+
}
58+
}
59+
60+
return false;
61+
}
62+
}

src/PTS/SymfonyDiLoader/FactoryContainer.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __construct(string $classLoader, FileLocatorInterface $locator)
2121
}
2222

2323
/**
24-
* @param array $configs
24+
* @param string[] $configs
2525
*
2626
* @return ContainerBuilder
2727
* @throws \Exception
@@ -35,6 +35,7 @@ public function create(array $configs): ContainerBuilder
3535
$loader->load($config);
3636
}
3737

38+
$builder->compile(true);
3839
return $builder;
3940
}
4041

src/PTS/SymfonyDiLoader/LoaderContainer.php

+82-74
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,47 @@
33

44
namespace PTS\SymfonyDiLoader;
55

6-
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\Config\FileLocator;
77
use Symfony\Component\DependencyInjection\ContainerBuilder;
88
use Symfony\Component\DependencyInjection\ContainerInterface;
99
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
1010
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
11+
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
1112

1213
class LoaderContainer implements LoaderContainerInterface
1314
{
14-
/** @var array */
15-
protected $configFiles = [];
16-
17-
/** @var FactoryContainer */
18-
protected $factory;
19-
/** @var ContainerInterface */
20-
protected $container;
21-
22-
/** @var string */
23-
protected $cacheFile;
24-
/** @var bool */
25-
protected $checkExpired = true;
26-
27-
/** @var string */
28-
protected $classContainer = 'AppContainer';
29-
30-
31-
public function __construct(array $configFiles, string $cacheFile, FactoryContainer $factory)
32-
{
33-
$this->configFiles = $configFiles;
34-
$this->cacheFile = $cacheFile;
35-
$this->factory = $factory;
36-
}
37-
38-
public function setCheckExpired($checkExpired = true): self
15+
/** @var array */
16+
protected $configFiles = [];
17+
18+
/** @var FactoryContainer */
19+
protected $factory;
20+
/** @var ContainerInterface */
21+
protected $container;
22+
/** @var CacheWatcher */
23+
protected $cacheWatcher;
24+
25+
/** @var string */
26+
protected $cacheFile;
27+
/** @var bool */
28+
protected $checkExpired = true;
29+
30+
/** @var string */
31+
protected $classContainer = 'AppContainer';
32+
33+
/**
34+
* @param string[] $configFiles
35+
* @param string $cacheFile
36+
* @param FactoryContainer|null $factory
37+
*/
38+
public function __construct(array $configFiles, string $cacheFile, FactoryContainer $factory = null)
39+
{
40+
$this->configFiles = $configFiles;
41+
$this->cacheFile = $cacheFile;
42+
$this->factory = $factory ?? new FactoryContainer(YamlFileLoader::class, new FileLocator);
43+
$this->cacheWatcher = new CacheWatcher;
44+
}
45+
46+
public function setCheckExpired(bool $checkExpired = true): self
3947
{
4048
$this->checkExpired = $checkExpired;
4149
return $this;
@@ -48,40 +56,44 @@ public function setCheckExpired($checkExpired = true): self
4856
public function getContainer(): ContainerInterface
4957
{
5058
if ($this->container === null) {
51-
$container = $this->createContainer();
52-
$this->setContainer($container);
59+
$container = $this->tryGetContainerFromCache($this->cacheFile, $this->configFiles);
60+
$container = $container ?? $this->createContainer($this->configFiles, $this->cacheFile, $this->classContainer);
61+
$this->container = $container;
5362
}
5463

5564
return $this->container;
5665
}
5766

58-
public function setContainer(ContainerInterface $container): self
67+
/**
68+
* @param string[] $configs
69+
* @param string $cacheFile
70+
* @param string $class
71+
*
72+
* @return ContainerInterface
73+
* @throws \Exception
74+
*/
75+
protected function createContainer(array $configs, string $cacheFile, string $class): ContainerInterface
5976
{
60-
$this->container = $container;
61-
return $this;
62-
}
63-
64-
protected function getFactory(): FactoryContainer
65-
{
66-
return $this->factory;
67-
}
68-
69-
/**
70-
* @return ContainerInterface
71-
* @throws \Exception
72-
*/
73-
protected function createContainer(): ContainerInterface
74-
{
75-
$appContainer = $this->getContainerFromCache($this->cacheFile, $this->configFiles);
76-
77-
if (!($appContainer instanceof ContainerInterface)) {
78-
$appContainer = $this->getFactory()->create($this->configFiles);
79-
$this->dump($this->cacheFile, $this->classContainer, $appContainer);
80-
}
77+
$appContainer = $this->factory->create($configs);
78+
$this->dump($cacheFile, $class, $appContainer);
79+
$this->dumpMeta($cacheFile . '.meta', $configs);
8180

8281
return $appContainer;
8382
}
8483

84+
/**
85+
* @param string $filePath
86+
* @param string[] $configFiles
87+
*/
88+
protected function dumpMeta(string $filePath, array $configFiles): void
89+
{
90+
try {
91+
file_put_contents($filePath, serialize($configFiles));
92+
} catch (\Throwable $throwable) {
93+
throw new \RuntimeException('Can`t dump meta for DI container', 0, $throwable);
94+
}
95+
}
96+
8597
/**
8698
* @param string $filePath
8799
* @param string $className
@@ -91,43 +103,39 @@ protected function createContainer(): ContainerInterface
91103
*/
92104
protected function dump(string $filePath, string $className, ContainerBuilder $container): void
93105
{
94-
$container->compile(true);
95106
$dumper = new PhpDumper($container);
96-
file_put_contents($filePath, $dumper->dump([
97-
'class' => $className,
98-
]));
107+
108+
try {
109+
file_put_contents($filePath, $dumper->dump([
110+
'class' => $className,
111+
]));
112+
} catch (\Throwable $throwable) {
113+
throw new \RuntimeException('Can`t dump cache for DI container', 0, $throwable);
114+
}
99115
}
100116

101-
protected function getContainerFromCache($fileCache, $configs): ?Container
117+
/**
118+
* @param string $fileCache
119+
* @param string[] $configs
120+
*
121+
* @return null|ContainerInterface
122+
*/
123+
protected function tryGetContainerFromCache(string $fileCache, array $configs): ?ContainerInterface
102124
{
103125
if (!file_exists($fileCache)) {
104126
return null;
105127
}
106128

107-
if ($this->checkExpired && $this->isExpired($fileCache, $configs)) {
129+
if ($this->checkExpired && !$this->getWatcher()->isActualCache($fileCache, $configs)) {
108130
return null;
109131
}
110132

111133
require_once $fileCache;
112134
return new $this->classContainer;
113135
}
114136

115-
/**
116-
* @param string $containerPath
117-
* @param string[] $configs
118-
*
119-
* @return bool
120-
*/
121-
protected function isExpired(string $containerPath, array $configs): bool
122-
{
123-
$cacheTime = filemtime($containerPath);
124-
125-
foreach ($configs as $config) {
126-
if ($cacheTime < filemtime($config)) {
127-
return true;
128-
}
129-
}
130-
131-
return false;
132-
}
137+
protected function getWatcher(): CacheWatcher
138+
{
139+
return $this->cacheWatcher;
140+
}
133141
}

test/phpunit.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit bootstrap="./../vendor/autoload.php" colors="true">
33
<testsuites>
4-
<testsuite>
4+
<testsuite name="unit">
55
<directory>./unit</directory>
66
</testsuite>
77
</testsuites>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace PTS\SymfonyDiLoader\Unit\CacheWatcher;
5+
6+
use org\bovigo\vfs\vfsStream;
7+
use PHPUnit\Framework\TestCase;
8+
use PTS\SymfonyDiLoader\CacheWatcher;
9+
10+
class GetMetaCacheTest extends TestCase
11+
{
12+
13+
/**
14+
* @inheritdoc
15+
*/
16+
public function tearDown()
17+
{
18+
parent::tearDown();
19+
unset($this->fs);
20+
}
21+
22+
public function testNotFound(): void
23+
{
24+
$this->expectException(\RuntimeException::class);
25+
$this->expectExceptionMessage('Can`t read meta for DI container');
26+
27+
$watcher = new CacheWatcher;
28+
$watcher->isActualCache('unknownCachePath.php', ['conf1']);
29+
}
30+
31+
/**
32+
* @param array $expected
33+
* @param array $configs
34+
*
35+
* @throws \ReflectionException
36+
*
37+
* @dataProvider dataProvider
38+
*/
39+
public function testGet(array $expected, array $configs): void
40+
{
41+
$watcher = new CacheWatcher;
42+
$filePath = $this->createMetaCache($configs, 'pathToFileCache');
43+
44+
$method = new \ReflectionMethod(CacheWatcher::class, 'getMetaCache');
45+
$method->setAccessible(true);
46+
$actual = $method->invoke($watcher, $filePath);
47+
48+
static::assertSame($expected, $actual);
49+
}
50+
51+
/**
52+
* @param string[] $configs
53+
* @param string $filePath
54+
*
55+
* @return string
56+
*/
57+
protected function createMetaCache(array $configs, string $filePath): string
58+
{
59+
$fs = vfsStream::setup('/temp/di-loader');
60+
61+
$filePathInMemory = vfsStream::newFile($filePath)
62+
->at($fs)
63+
->setContent(serialize($configs))
64+
->url();
65+
66+
return $filePathInMemory;
67+
}
68+
69+
public function dataProvider(): array
70+
{
71+
return [
72+
'same config' => [
73+
['/some/conf1.yml', '/some/conf2.yml'],
74+
['/some/conf1.yml', '/some/conf2.yml'],
75+
]
76+
];
77+
}
78+
}

0 commit comments

Comments
 (0)