1+ <?php
2+
3+ namespace Proklung \Redis \DI ;
4+
5+ use InvalidArgumentException ;
6+ use Symfony \Bridge \ProxyManager \LazyProxy \PhpDumper \ProxyDumper ;
7+ use Symfony \Component \Config \ConfigCache ;
8+ use Symfony \Component \DependencyInjection \Container ;
9+ use Symfony \Component \DependencyInjection \ContainerBuilder ;
10+ use Symfony \Component \DependencyInjection \Dumper \PhpDumper ;
11+ use Symfony \Component \Filesystem \Filesystem ;
12+ use Throwable ;
13+
14+ /**
15+ * Class CompilerContainer
16+ * @package Proklung\Redis\DI
17+ *
18+ * @since 14.07.2021
19+ */
20+ class CompilerContainer
21+ {
22+ /**
23+ * @param ContainerBuilder $container Контейнер.
24+ * @param string $cacheDirectory Директория кэша.
25+ * @param string $filename Файл кэша.
26+ * @param string $environment Окружение.
27+ * @param boolean $debug Режим отладки.
28+ * @param callable $initializerContainer Инициализатор контейнера.
29+ *
30+ * @return Container
31+ */
32+ public function cacheContainer (
33+ ContainerBuilder $ container ,
34+ string $ cacheDirectory ,
35+ string $ filename ,
36+ string $ environment ,
37+ bool $ debug ,
38+ callable $ initializerContainer
39+ ) : Container {
40+ $ this ->createCacheDirectory ($ cacheDirectory );
41+
42+ $ compiledContainerFile = $ cacheDirectory . '/ ' . $ filename ;
43+
44+ $ containerConfigCache = new ConfigCache ($ compiledContainerFile , true );
45+
46+ // Класс скомпилированного контейнера.
47+ $ classCompiledContainerName = $ this ->getContainerClass ($ environment , $ debug ) . md5 ($ filename );
48+
49+ if (!$ containerConfigCache ->isFresh ()) {
50+ // Загрузить, инициализировать и скомпилировать контейнер.
51+ $ newContainer = $ initializerContainer ();
52+
53+ // Блокировка на предмет конкурентных запросов.
54+ $ lockFile = $ cacheDirectory . '/container.lock ' ;
55+
56+ // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
57+ $ errorLevel = error_reporting (\E_ALL ^ \E_WARNING );
58+
59+ $ lock = false ;
60+ try {
61+ if ($ lock = fopen ($ lockFile , 'w ' )) {
62+ flock ($ lock , \LOCK_EX | \LOCK_NB , $ wouldBlock );
63+ if (!flock ($ lock , $ wouldBlock ? \LOCK_SH : \LOCK_EX )) {
64+ fclose ($ lock );
65+ @unlink ($ lockFile );
66+ $ lock = null ;
67+ }
68+ } else {
69+ // Если в файл контейнера уже что-то пишется, то вернем свежую копию контейнера.
70+ flock ($ lock , \LOCK_UN );
71+ fclose ($ lock );
72+ @unlink ($ lockFile );
73+
74+ return $ newContainer ;
75+ }
76+ } catch (Throwable $ e ) {
77+ } finally {
78+ error_reporting ($ errorLevel );
79+ }
80+
81+ $ this ->dumpContainer ($ containerConfigCache , $ container , $ classCompiledContainerName , $ debug );
82+
83+ if ($ lock ) {
84+ flock ($ lock , \LOCK_UN );
85+ fclose ($ lock );
86+ @unlink ($ lockFile );
87+ }
88+ }
89+
90+ // Подключение скомпилированного контейнера.
91+ /** @noinspection PhpIncludeInspection */
92+ require_once $ compiledContainerFile ;
93+
94+ $ classCompiledContainerName = '\\' .$ classCompiledContainerName ;
95+
96+ return new $ classCompiledContainerName ();
97+ }
98+
99+ /**
100+ * Если надо создать директорию для компилированного контейнера.
101+ *
102+ * @param string $dir
103+ *
104+ * @return void
105+ */
106+ private function createCacheDirectory (string $ dir ) : void
107+ {
108+ $ filesystem = new Filesystem ();
109+
110+ if (!$ filesystem ->exists ($ dir )) {
111+ $ filesystem ->mkdir ($ dir );
112+ }
113+ }
114+
115+ /**
116+ * Gets the container class.
117+ *
118+ * @param string $env
119+ * @param boolean $debug
120+ *
121+ * @return string The container class.
122+ */
123+ private function getContainerClass (string $ env , bool $ debug ) : string
124+ {
125+ $ class = static ::class;
126+ $ class = false !== strpos ($ class , "@anonymous \0" ) ? get_parent_class ($ class ).str_replace ('. ' , '_ ' , ContainerBuilder::hash ($ class ))
127+ : $ class ;
128+ $ class = str_replace ('\\' , '_ ' , $ class ).ucfirst ($ env ).($ debug ? 'Debug ' : '' ).'Container ' ;
129+
130+ if (!preg_match ('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/ ' , $ class )) {
131+ throw new InvalidArgumentException (
132+ sprintf ('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names. ' , $ this ->environment )
133+ );
134+ }
135+
136+ return $ class ;
137+ }
138+
139+ /**
140+ * Dumps the service container to PHP code in the cache.
141+ *
142+ * @param ConfigCache $cache Кэш.
143+ * @param ContainerBuilder $container Контейнер.
144+ * @param string $class The name of the class to generate.
145+ * @param boolean $debug Отладка.
146+ *
147+ * @return void
148+ */
149+ private function dumpContainer (ConfigCache $ cache , ContainerBuilder $ container , string $ class , bool $ debug ) : void
150+ {
151+ // Опция - дампить как файлы. По умолчанию - нет.
152+ $ asFiles = false ;
153+ if ($ container ->hasParameter ('container.dumper.inline_factories ' )) {
154+ $ asFiles = $ container ->getParameter ('container.dumper.inline_factories ' );
155+ }
156+
157+ $ dumper = new PhpDumper ($ container );
158+ if (class_exists (\ProxyManager \Configuration::class) && class_exists (ProxyDumper::class)) {
159+ $ dumper ->setProxyDumper (new ProxyDumper ());
160+ }
161+
162+ $ content = $ dumper ->dump (
163+ [
164+ 'class ' => $ class ,
165+ 'file ' => $ cache ->getPath (),
166+ 'as_files ' => $ asFiles ,
167+ 'debug ' => $ debug ,
168+ 'build_time ' => $ container ->hasParameter ('kernel.container_build_time ' )
169+ ? $ container ->getParameter ('kernel.container_build_time ' ) : time (),
170+ 'preload_classes ' => [],
171+ ]
172+ );
173+
174+ // Если as_files = true.
175+ if (is_array ($ content )) {
176+ $ rootCode = array_pop ($ content );
177+ $ dir = \dirname ($ cache ->getPath ()).'/ ' ;
178+
179+ $ filesystem = new Filesystem ();
180+
181+ foreach ($ content as $ file => $ code ) {
182+ $ filesystem ->dumpFile ($ dir .$ file , $ code );
183+ @chmod ($ dir .$ file , 0666 & ~umask ());
184+ }
185+
186+ $ legacyFile = \dirname ($ dir .key ($ content )).'.legacy ' ;
187+ if (is_file ($ legacyFile )) {
188+ @unlink ($ legacyFile );
189+ }
190+
191+ $ content = $ rootCode ;
192+ }
193+
194+ $ cache ->write (
195+ $ content , // @phpstan-ignore-line
196+ $ container ->getResources ()
197+ );
198+ }
199+ }
0 commit comments