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); |
||
| 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); |
||
| 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); |
||
| 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) |
||
|
0 ignored issues
–
show
|
|||
| 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 | } |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.