Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #18894 [Cache] Added PhpFilesAdapter (trakos, nicolas-grekas)
This PR was merged into the 3.2-dev branch. Discussion ---------- [Cache] Added PhpFilesAdapter | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - This is taking over #18832. With a warm cache I get these numbers consistently (PhpArrayAdapter being the implem in #18823 ): ``` Fetching randomly 5000 items 10000 times: Symfony\Component\Cache\Adapter\FilesystemAdapter: 0.1367, 2 megabytes Symfony\Component\Cache\Adapter\PhpArrayAdapter: 0.0071, 2 megabytes Symfony\Component\Cache\Adapter\PhpFilesAdapter: 0.0389, 2 megabytes Symfony\Component\Cache\Adapter\ApcuAdapter: 0.0361, 2 megabytes ``` This means that the PhpArrayAdapter should be used first, then ApcuAdapter preferred over PhpFilesAdapter, then FilesystemAdapter. This is what AbstractAdapter does here. Also note that to get the cache working, one should stay within the limits defined by the following ini settings: - memory_limit - apc.shm_size - opcache.memory_consumption - opcache.interned_strings_buffer - opcache.max_accelerated_files Commits ------- 8983e83 [Cache] Optimize & wire PhpFilesAdapter 14bcd79 [Cache] Added PhpFilesAdapter
- Loading branch information
Showing
7 changed files
with
286 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Cache\Adapter; | ||
|
||
use Symfony\Component\Cache\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* @author Nicolas Grekas <p@tchwork.com> | ||
*/ | ||
trait FilesystemAdapterTrait | ||
{ | ||
private $directory; | ||
private $tmp; | ||
|
||
private function init($namespace, $directory) | ||
{ | ||
if (!isset($directory[0])) { | ||
$directory = sys_get_temp_dir().'/symfony-cache'; | ||
} | ||
if (isset($namespace[0])) { | ||
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { | ||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); | ||
} | ||
$directory .= '/'.$namespace; | ||
} | ||
if (!file_exists($dir = $directory.'/.')) { | ||
@mkdir($directory, 0777, true); | ||
} | ||
if (false === $dir = realpath($dir)) { | ||
throw new InvalidArgumentException(sprintf('Cache directory does not exist (%s)', $directory)); | ||
} | ||
if (!is_writable($dir .= DIRECTORY_SEPARATOR)) { | ||
throw new InvalidArgumentException(sprintf('Cache directory is not writable (%s)', $directory)); | ||
} | ||
// On Windows the whole path is limited to 258 chars | ||
if ('\\' === DIRECTORY_SEPARATOR && strlen($dir) > 234) { | ||
throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory)); | ||
} | ||
|
||
$this->directory = $dir; | ||
$this->tmp = $this->directory.uniqid('', true); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doClear($namespace) | ||
{ | ||
$ok = true; | ||
|
||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { | ||
$ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok; | ||
} | ||
|
||
return $ok; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doDelete(array $ids) | ||
{ | ||
$ok = true; | ||
|
||
foreach ($ids as $id) { | ||
$file = $this->getFile($id); | ||
$ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok; | ||
} | ||
|
||
return $ok; | ||
} | ||
|
||
private function write($file, $data, $expiresAt = null) | ||
{ | ||
if (false === @file_put_contents($this->tmp, $data)) { | ||
return false; | ||
} | ||
if (null !== $expiresAt) { | ||
@touch($this->tmp, $expiresAt); | ||
} | ||
|
||
if (@rename($this->tmp, $file)) { | ||
return true; | ||
} | ||
@unlink($this->tmp); | ||
|
||
return false; | ||
} | ||
|
||
private function getFile($id, $mkdir = false) | ||
{ | ||
$hash = str_replace('/', '-', base64_encode(md5(static::class.$id, true))); | ||
$dir = $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR; | ||
|
||
if ($mkdir && !file_exists($dir)) { | ||
@mkdir($dir, 0777, true); | ||
} | ||
|
||
return $dir.substr($hash, 2, -2); | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Cache\Adapter; | ||
|
||
use Symfony\Component\Cache\Exception\CacheException; | ||
use Symfony\Component\Cache\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* @author Piotr Stankowski <git@trakos.pl> | ||
* @author Nicolas Grekas <p@tchwork.com> | ||
*/ | ||
class PhpFilesAdapter extends AbstractAdapter | ||
{ | ||
use FilesystemAdapterTrait; | ||
|
||
private $includeHandler; | ||
|
||
public static function isSupported() | ||
{ | ||
return function_exists('opcache_compile_file') && ini_get('opcache.enable'); | ||
} | ||
|
||
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) | ||
{ | ||
if (!static::isSupported()) { | ||
throw new CacheException('OPcache is not enabled'); | ||
} | ||
parent::__construct('', $defaultLifetime); | ||
$this->init($namespace, $directory); | ||
|
||
$e = new \Exception(); | ||
$this->includeHandler = function () use ($e) { throw $e; }; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doFetch(array $ids) | ||
{ | ||
$values = array(); | ||
$now = time(); | ||
|
||
set_error_handler($this->includeHandler); | ||
try { | ||
foreach ($ids as $id) { | ||
try { | ||
$file = $this->getFile($id); | ||
list($expiresAt, $values[$id]) = include $file; | ||
if ($now >= $expiresAt) { | ||
unset($values[$id]); | ||
} | ||
} catch (\Exception $e) { | ||
continue; | ||
} | ||
} | ||
} finally { | ||
restore_error_handler(); | ||
} | ||
|
||
foreach ($values as $id => $value) { | ||
if ('N;' === $value) { | ||
$values[$id] = null; | ||
} elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { | ||
$values[$id] = unserialize($value); | ||
} | ||
} | ||
|
||
return $values; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doHave($id) | ||
{ | ||
return (bool) $this->doFetch(array($id)); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function doSave(array $values, $lifetime) | ||
{ | ||
$ok = true; | ||
$data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, ''); | ||
|
||
foreach ($values as $id => $value) { | ||
if (null === $value || is_object($value)) { | ||
$value = serialize($value); | ||
} elseif (is_array($value)) { | ||
$serialized = serialize($value); | ||
$unserialized = unserialize($serialized); | ||
// Store arrays serialized if they contain any objects or references | ||
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { | ||
$value = $serialized; | ||
} | ||
} elseif (is_string($value)) { | ||
// Serialize strings if they could be confused with serialized objects or arrays | ||
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { | ||
$value = serialize($value); | ||
} | ||
} elseif (!is_scalar($value)) { | ||
throw new InvalidArgumentException(sprintf('Value of type "%s" is not serializable', $key, gettype($value))); | ||
} | ||
|
||
$data[1] = $value; | ||
$file = $this->getFile($id, true); | ||
$ok = $this->write($file, '<?php return '.var_export($data, true).';') && $ok; | ||
@opcache_compile_file($file); | ||
} | ||
|
||
return $ok; | ||
} | ||
} |
Oops, something went wrong.