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++) { |
||||||
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 | } |