sebkrueger /
CachingProxy
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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
Loading history...
|
|||||||
| 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 | } |