Skip to content

[Cache] Added PhpFilesAdapter #18894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ before_install:
- if [[ $PHP != hhvm ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi
- if [[ ! $skip ]]; then echo memory_limit = -1 >> $INI_FILE; fi
- if [[ ! $skip ]]; then echo session.gc_probability = 0 >> $INI_FILE; fi
- if [[ ! $skip ]]; then echo opcache.enable_cli = 1 >> $INI_FILE; fi
- if [[ ! $skip && $PHP = 5.* ]]; then echo extension = mongo.so >> $INI_FILE; fi
- if [[ ! $skip && $PHP = 5.* ]]; then echo extension = memcache.so >> $INI_FILE; fi
- if [[ ! $skip && $PHP = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.10 && echo apc.enable_cli = 1 >> $INI_FILE); fi
Expand Down
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ install:
- IF %PHP%==1 echo date.timezone="UTC" >> php.ini-min
- IF %PHP%==1 echo extension_dir=ext >> php.ini-min
- IF %PHP%==1 copy /Y php.ini-min php.ini-max
- IF %PHP%==1 echo zend_extension=php_opcache.dll >> php.ini-max
- IF %PHP%==1 echo opcache.enable_cli=1 >> php.ini-max
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini-max
- IF %PHP%==1 echo extension=php_apcu.dll >> php.ini-max
- IF %PHP%==1 echo apc.enable_cli=1 >> php.ini-max
Expand Down
9 changes: 9 additions & 0 deletions src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ function ($deferred, $namespace, &$expiredIds) {

public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
if (!ApcuAdapter::isSupported() && PhpFilesAdapter::isSupported()) {
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$opcache->setLogger($logger);
}

return $opcache;
}

$fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$fs->setLogger($logger);
Expand Down
82 changes: 3 additions & 79 deletions src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,17 @@

namespace Symfony\Component\Cache\Adapter;

use Symfony\Component\Cache\Exception\InvalidArgumentException;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class FilesystemAdapter extends AbstractAdapter
{
private $directory;
use FilesystemAdapterTrait;

public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
parent::__construct('', $defaultLifetime);

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('FilesystemAdapter 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->init($namespace, $directory);
}

/**
Expand Down Expand Up @@ -91,68 +65,18 @@ protected function doHave($id)
return file_exists($file) && (@filemtime($file) > time() || $this->doFetch(array($id)));
}

/**
* {@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;
}

/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
$ok = true;
$expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX;
$tmp = $this->directory.uniqid('', true);

foreach ($values as $id => $value) {
$file = $this->getFile($id, true);

$value = $expiresAt."\n".rawurlencode($id)."\n".serialize($value);
if (false !== @file_put_contents($tmp, $value)) {
@touch($tmp, $expiresAt);
$ok = @rename($tmp, $file) && $ok;
} else {
$ok = false;
}
$ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
}

return $ok;
}

private function getFile($id, $mkdir = false)
{
$hash = str_replace('/', '-', base64_encode(md5($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);
}
}
110 changes: 110 additions & 0 deletions src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php
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 src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php
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;
}
}
Loading