1
+ <?php
2
+
3
+ namespace Proklung \RabbitMq \Integration \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\RabbitMq\Integration\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