1 | <?php |
||||||
2 | /*------------------------------------------------------------------------------ |
||||||
3 | |||||||
4 | Project : CachingProxy |
||||||
5 | Filename : src/AbstractCachingProxy.php |
||||||
6 | Autor : (c) Sebastian Krüger <[email protected]> |
||||||
7 | Date : 15.09.2013 |
||||||
8 | |||||||
9 | For the full copyright and license information, please view the LICENSE |
||||||
10 | file that was distributed with this source code. |
||||||
11 | |||||||
12 | Description: Basisklasse die einen Mechanismus zu Cachen von Dateien auf dem |
||||||
13 | Server implemeniert. Anwendung ist später für CSS und Javscript |
||||||
14 | Dateien vorgesehen |
||||||
15 | |||||||
16 | Die zu Cachenden Dateien werden zu einer gesammten Datei zusammengefasst |
||||||
17 | Externe Dateien werden nicht zusammengefasst, sondern vorerst einfach nur |
||||||
18 | als Einbindung ausgegeben. Falls vorhanden wird die minifizierte Version der |
||||||
19 | Datei vorgezogen. Zu guter letzt werden die Dateien auch noch per GZ gepackt |
||||||
20 | um statische gepackte Dateien anbieten zu können |
||||||
21 | |||||||
22 | ----------------------------------------------------------------------------*/ |
||||||
23 | |||||||
24 | namespace secra\CachingProxy; |
||||||
25 | |||||||
26 | abstract class AbstractCachingProxy |
||||||
27 | { |
||||||
28 | protected $internfilelist = array(); // array with files that should be cached later |
||||||
29 | private $externfilelist = array(); // array with extern files |
||||||
30 | |||||||
31 | protected $docrootpath = null; // webserver document root path |
||||||
32 | protected $cachepath = null; // absolut path on webserver were cached files should be placed |
||||||
33 | protected $relcachepath = null; // relative cachepath for scripttags in html |
||||||
34 | |||||||
35 | // In debugmode every file will be include in a single tag without modification |
||||||
36 | protected $debugmode = false; |
||||||
37 | |||||||
38 | /** |
||||||
39 | * Implement later to set the document rootpath and cachepath |
||||||
40 | * |
||||||
41 | * @param string $webserverRootPath absolut path to webserver root |
||||||
42 | * @param string $cachePath path to cachefile location based on webserver root path |
||||||
43 | * |
||||||
44 | * @return AbstractCachingProxy|null objectinstance |
||||||
45 | */ |
||||||
46 | public function __construct($webserverRootPath, $cachePath) |
||||||
47 | { |
||||||
48 | $this->setWebserverRootPath($webserverRootPath); |
||||||
49 | $this->setCachepath($cachePath); |
||||||
50 | } |
||||||
51 | |||||||
52 | /** |
||||||
53 | * Implement later html code return |
||||||
54 | * |
||||||
55 | * Implement this to get the specific html head code |
||||||
56 | * |
||||||
57 | * @codeCoverageIgnore |
||||||
58 | * |
||||||
59 | * @return string the html scripttag code |
||||||
60 | */ |
||||||
61 | abstract public function getIncludeHtml(); |
||||||
62 | |||||||
63 | /** |
||||||
64 | * Delivers extension for cached files |
||||||
65 | * |
||||||
66 | * @codeCoverageIgnore |
||||||
67 | * |
||||||
68 | * @return string file extension |
||||||
69 | * |
||||||
70 | */ |
||||||
71 | abstract protected function getCacheFileExtension(); |
||||||
72 | |||||||
73 | /** |
||||||
74 | * Add files to proxy |
||||||
75 | * |
||||||
76 | * Add intern, project relative files or extern files, on different domain |
||||||
77 | * to the filelist |
||||||
78 | * |
||||||
79 | * @param string $filename the filepath/URL to script |
||||||
80 | * |
||||||
81 | * @return boolean false on error |
||||||
82 | */ |
||||||
83 | public function addFile($filename) |
||||||
84 | { |
||||||
85 | // Fügt eine Datei zur Cacheliste hinzu, es wird hier schon nach internen oder |
||||||
86 | // Externen Dateien unterschieden beginnen z.B. mit http, https, ftp und dann :// |
||||||
87 | // or protocoll less // links |
||||||
88 | if (!preg_match("#^[a-z]{3,5}://#i", $filename) && !preg_match("#^//#i", $filename)) { |
||||||
89 | // intern files, work for the cache |
||||||
90 | $absolutfilename = self::makeAbsolutPath($filename); |
||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
91 | if (!file_exists($absolutfilename)) { |
||||||
92 | // the file did't exist |
||||||
93 | return false; |
||||||
94 | } |
||||||
95 | |||||||
96 | // Falls möglich Minifizierte Version der Datei benutzen |
||||||
97 | $minfilename = self::makeMinifiPath($absolutfilename); |
||||||
0 ignored issues
–
show
The method
secra\CachingProxy\Abstr...Proxy::makeMinifiPath() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
98 | |||||||
99 | if (file_exists($minfilename)) { |
||||||
100 | // Es scheint jemand die Datei gepackt zu haben |
||||||
101 | $absolutfilename = $minfilename; |
||||||
102 | } |
||||||
103 | |||||||
104 | $this->internfilelist[] = $absolutfilename; |
||||||
105 | } else { |
||||||
106 | // Ist wohl eine Externe Pfadangabe, kann nicht gechached werden |
||||||
107 | $this->externfilelist[] = $filename; |
||||||
108 | } |
||||||
109 | return true; |
||||||
110 | } |
||||||
111 | |||||||
112 | /** |
||||||
113 | * Return list of all files can be include |
||||||
114 | * |
||||||
115 | * First deliver all intern then all extern files |
||||||
116 | * the user can decide by himself, what he would like to do with the list |
||||||
117 | * |
||||||
118 | * @return string[] list of files |
||||||
119 | */ |
||||||
120 | public function getIncludeFileset() |
||||||
121 | { |
||||||
122 | // Return list of all files that can include |
||||||
123 | // first all intern then all extern files |
||||||
124 | // the user can decide by himself, what he would like to do with the list |
||||||
125 | |||||||
126 | // Exclude double files from filelist |
||||||
127 | $this->internfilelist = array_unique($this->internfilelist); |
||||||
128 | $this->externfilelist = array_unique($this->externfilelist); |
||||||
129 | |||||||
130 | $returnfilelist = array(); |
||||||
131 | |||||||
132 | // generate cachefile |
||||||
133 | // the return will not be used in debugmode, but generate the files anyway |
||||||
134 | $oneModifiedCacheFile = $this->getCacheFile(); |
||||||
135 | |||||||
136 | if ($this->debugmode===false) { |
||||||
137 | // put intern files into the cached version |
||||||
138 | if ($oneModifiedCacheFile!=null) { |
||||||
139 | // only replace the intern file list, if theres intern files and the modified cache file exits |
||||||
140 | $returnfilelist[] = $oneModifiedCacheFile; |
||||||
141 | } |
||||||
142 | } else { |
||||||
143 | // we are in debugmode! |
||||||
144 | // only put the internfiles to the list of returned files |
||||||
145 | foreach ($this->internfilelist as $file) { |
||||||
146 | // strip the absolut dir for inclusion and put it to the list |
||||||
147 | // use the $ as reg_exp separater because don't expect it in path |
||||||
148 | $returnfilelist[] = "/".preg_replace("$^".($this->docrootpath)."$", "", $file); |
||||||
149 | } |
||||||
150 | } |
||||||
151 | |||||||
152 | // extern files will only add to the list |
||||||
153 | foreach ($this->externfilelist as $file) { |
||||||
154 | $returnfilelist[] = $file; |
||||||
155 | } |
||||||
156 | |||||||
157 | return $returnfilelist; |
||||||
158 | } |
||||||
159 | |||||||
160 | /** |
||||||
161 | * Sweet as simple ... activate the debugmode |
||||||
162 | * |
||||||
163 | * @return null |
||||||
164 | */ |
||||||
165 | public function enableDebugmode() |
||||||
166 | { |
||||||
167 | // sweet as simple ... activate the debugmode |
||||||
168 | $this->debugmode=true; |
||||||
169 | return null; |
||||||
170 | } |
||||||
171 | |||||||
172 | /** |
||||||
173 | * belive it or not ... deactivate the debugmode |
||||||
174 | * |
||||||
175 | * @return null |
||||||
176 | */ |
||||||
177 | public function disableDebugmode() |
||||||
178 | { |
||||||
179 | $this->debugmode=false; |
||||||
180 | return null; |
||||||
181 | } |
||||||
182 | |||||||
183 | /** |
||||||
184 | * Set the relative Cachepath |
||||||
185 | * use simple $_SERVER["DOCUMENT_ROOT"] to set this value |
||||||
186 | * |
||||||
187 | * Set absolut webserver rootpath |
||||||
188 | * |
||||||
189 | * @param string $documentRootPath path to webserverroot |
||||||
190 | * |
||||||
191 | * @return boolean false on error |
||||||
192 | */ |
||||||
193 | protected function setWebserverRootPath($documentRootPath) |
||||||
194 | { |
||||||
195 | // Reset the documentrootpath |
||||||
196 | $this->docrootpath = null; |
||||||
197 | |||||||
198 | // Add trailing slash if not there |
||||||
199 | if (!preg_match("#/$#", $documentRootPath)) { |
||||||
200 | $documentRootPath .= "/"; |
||||||
201 | } |
||||||
202 | |||||||
203 | if (file_exists($documentRootPath)) { |
||||||
204 | $this->docrootpath = $documentRootPath; |
||||||
205 | return true; |
||||||
206 | } else { |
||||||
207 | return false; |
||||||
208 | } |
||||||
209 | } |
||||||
210 | |||||||
211 | /** |
||||||
212 | * Set path to cachefile folder |
||||||
213 | * |
||||||
214 | * The cachingpath must be set relativ from docroot of project |
||||||
215 | * |
||||||
216 | * @param string $cachepath path to cachefilefolder |
||||||
217 | * |
||||||
218 | * @return boolean true if cachefolder exist, false if not |
||||||
219 | */ |
||||||
220 | protected function setCachepath($cachepath) |
||||||
221 | { |
||||||
222 | |||||||
223 | // try to make cachepath absolut |
||||||
224 | $absolutcachepath = self::makeAbsolutPath($cachepath); |
||||||
0 ignored issues
–
show
The method
secra\CachingProxy\Abstr...roxy::makeAbsolutPath() is not static, but was called statically.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
225 | |||||||
226 | // check if path exist could be false/null because the use of makeAbsolutPath()!! |
||||||
227 | if (is_dir($absolutcachepath)) { |
||||||
228 | |||||||
229 | // extra check on trailing slash of $absolutcachepath because of realpath function use |
||||||
230 | if (!preg_match("#/$#", $absolutcachepath)) { |
||||||
231 | $absolutcachepath .= "/"; |
||||||
232 | } |
||||||
233 | |||||||
234 | $this->cachepath=$absolutcachepath; |
||||||
235 | |||||||
236 | // check if path has trailing slash, if not add now |
||||||
237 | if (!preg_match("#/$#", $cachepath)) { |
||||||
238 | $cachepath .= "/"; |
||||||
239 | } |
||||||
240 | |||||||
241 | // check if path begin with slash, if not add it now because |
||||||
242 | // later every intern files will include absolut to the webserver |
||||||
243 | // root path, also importent, if mode rewrite is in use on the server |
||||||
244 | if (!preg_match("#^/#", $cachepath)) { |
||||||
245 | $cachepath = "/".$cachepath; |
||||||
246 | } |
||||||
247 | |||||||
248 | $this->relcachepath=$cachepath; |
||||||
249 | return true; |
||||||
250 | } else { |
||||||
251 | // folder did't exist |
||||||
252 | // TODO: throw next time an error! |
||||||
253 | return false; |
||||||
254 | } |
||||||
255 | } |
||||||
256 | |||||||
257 | /** |
||||||
258 | * |
||||||
259 | * Predefined Function to do something content specific work in |
||||||
260 | * concrete classes right befor the minify process |
||||||
261 | * default do nothing and return the sting one2one |
||||||
262 | * |
||||||
263 | * @param string $filecontent filecontent to process |
||||||
264 | * @param string $filepath path to the file to convert it later |
||||||
265 | * |
||||||
266 | * @return string modified filecontent |
||||||
267 | */ |
||||||
268 | protected function modifyFilecontent($filecontent, $filepath) |
||||||
269 | { |
||||||
270 | // default -> return content one2one and ignore the $filepath |
||||||
271 | return $filecontent; |
||||||
272 | } |
||||||
273 | |||||||
274 | /** |
||||||
275 | * Relative to absolut path |
||||||
276 | * |
||||||
277 | * Convert relativ path webserver root path to |
||||||
278 | * absolut path from root in file system |
||||||
279 | * |
||||||
280 | * @param string $path relative path to webserver root |
||||||
281 | * |
||||||
282 | * @return string absolut path to webserver root |
||||||
283 | */ |
||||||
284 | private function makeAbsolutPath($path) |
||||||
285 | { |
||||||
286 | // Note, if file/folder don't exists, realpath will return false |
||||||
287 | return realpath($this->docrootpath.$path); |
||||||
288 | } |
||||||
289 | |||||||
290 | /** |
||||||
291 | * Add .min in file path |
||||||
292 | * |
||||||
293 | * Check if minified version of file exits |
||||||
294 | * is exits use it |
||||||
295 | * |
||||||
296 | * @param string $path path to notminified version of file |
||||||
297 | * |
||||||
298 | * @return string path to minified version of file |
||||||
299 | */ |
||||||
300 | private function makeMinifiPath($path) |
||||||
301 | { |
||||||
302 | // check if there's a minified version of file, if yes there min version will be used |
||||||
303 | |||||||
304 | // split path at the dots |
||||||
305 | $splitpath = explode(".", $path); |
||||||
306 | |||||||
307 | $newfragments = array(); |
||||||
308 | |||||||
309 | for ($i=0; $i<count($splitpath); $i++) { |
||||||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||||||
310 | if ($i==(count($splitpath)-1)) { |
||||||
311 | // insert "min" bevor last element (file ending) |
||||||
312 | $newfragments[]="min"; |
||||||
313 | } |
||||||
314 | $newfragments[]=$splitpath[$i]; |
||||||
315 | } |
||||||
316 | |||||||
317 | // now put the puzzelpices together |
||||||
318 | return implode(".", $newfragments); |
||||||
319 | } |
||||||
320 | |||||||
321 | /** |
||||||
322 | * Return one cached file |
||||||
323 | * |
||||||
324 | * Check if cached and gzipped version of file exits |
||||||
325 | * if not convert all intern files to one file and |
||||||
326 | * write it to one cache |
||||||
327 | * |
||||||
328 | * @return string|false|null path to cached file or null if no intern files and false on error |
||||||
329 | */ |
||||||
330 | private function getCacheFile() |
||||||
331 | { |
||||||
332 | // ask if there file signature match with, requested files in the file list |
||||||
333 | $cachefilesignature = $this->calculateFileSignature(); |
||||||
334 | |||||||
335 | // connect the path, related to document root |
||||||
336 | $cachefile = $cachefilesignature.$this->getCacheFileExtension(); |
||||||
337 | |||||||
338 | $absolutcachepath = $this->cachepath.$cachefile; |
||||||
339 | |||||||
340 | // set return value null in case there are no internfiles |
||||||
341 | $returnfile = null; |
||||||
342 | |||||||
343 | if ($cachefilesignature!=null) { |
||||||
344 | // The cachefilesignature has to be different from null to start |
||||||
345 | if (!file_exists($absolutcachepath)) { |
||||||
346 | // the file has never been written, write now -> the hard way! |
||||||
347 | // put files together |
||||||
348 | foreach ($this->internfilelist as $file) { |
||||||
349 | // read content of current file |
||||||
350 | // if overwritten, modfiy the content and put the files together in one string |
||||||
351 | $filecontent = $this->modifyFilecontent(file_get_contents($file), $file); |
||||||
352 | |||||||
353 | // to be safe, add new line |
||||||
354 | $filecontent .= "\n"; |
||||||
355 | |||||||
356 | // append content while writing and look file on other access tries! |
||||||
357 | if (file_put_contents($absolutcachepath, $filecontent, FILE_APPEND | LOCK_EX)===false) { |
||||||
358 | // TODO: this error check won't work well, maybe better use other function to write the file |
||||||
359 | return false; |
||||||
360 | } |
||||||
361 | } |
||||||
362 | // short delay to be safe |
||||||
363 | usleep(5000); |
||||||
364 | |||||||
365 | // now make the gzip version, once we on the way |
||||||
366 | file_put_contents($absolutcachepath.".gz", gzencode(file_get_contents($absolutcachepath), 9)); |
||||||
367 | } |
||||||
368 | |||||||
369 | // Files in Cachefolder still exits, assume we created it at another run |
||||||
370 | // don't create them once again only build the path an return it |
||||||
371 | $returnfile = $this->relcachepath.$cachefile; |
||||||
372 | } |
||||||
373 | |||||||
374 | return $returnfile; |
||||||
375 | } |
||||||
376 | |||||||
377 | /** |
||||||
378 | * Calculate a signature to detect file modification |
||||||
379 | * |
||||||
380 | * Calculate a signature of all intern files |
||||||
381 | * base parameters are filename and file modfied date |
||||||
382 | * hash function is md5 |
||||||
383 | * |
||||||
384 | * @return string|null signature |
||||||
385 | */ |
||||||
386 | private function calculateFileSignature() |
||||||
387 | { |
||||||
388 | // create a signature with all files in intern array structure |
||||||
389 | // to make it unified, every filname and filechange date will calculate |
||||||
390 | // together and return as md5 sum |
||||||
391 | $tempstingbase = ""; |
||||||
392 | |||||||
393 | foreach ($this->internfilelist as $file) { |
||||||
394 | $tempstingbase .= $file."->"; |
||||||
395 | $tempstingbase .= "(".filemtime($file).") "; |
||||||
396 | } |
||||||
397 | |||||||
398 | // when there are no intern files, function return null |
||||||
399 | $signature=null; |
||||||
400 | |||||||
401 | if (count($this->internfilelist)>0) { |
||||||
402 | // there are intern files - simple md5 should do, no secure risk |
||||||
403 | $signature = md5($tempstingbase); |
||||||
404 | } |
||||||
405 | |||||||
406 | return $signature; |
||||||
407 | } |
||||||
408 | |||||||
409 | // TODO: build cleanup function for old cached files |
||||||
410 | } |