Initial commit

This commit is contained in:
2021-03-23 13:54:38 +01:00
commit 82b142ff95
16941 changed files with 2617212 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# for php-coveralls
service_name: travis-ci
src_dir: lib
coverage_clover: build/logs/clover.xml

4
core/vendor/doctrine/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
vendor/
build/
phpunit.xml
composer.lock

44
core/vendor/doctrine/cache/.travis.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
language: php
sudo: false
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
services:
- riak
- mongodb
- memcached
- redis-server
before_install:
- if [[ $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != '7.0' ]]; then pecl install riak-beta; fi;
- if [[ $TRAVIS_PHP_VERSION =~ 5.[34] ]] ; then echo 'extension="apc.so"' >> ./tests/travis/php.ini; fi;
- if [[ $TRAVIS_PHP_VERSION =~ 5.[56] ]] ; then echo yes | pecl install apcu-4.0.10; fi;
- if [[ $TRAVIS_PHP_VERSION = 7.* ]] ; then pecl config-set preferred_state beta; echo yes | pecl install apcu; fi;
- if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]]; then phpenv config-add ./tests/travis/php.ini; fi;
install:
- travis_retry composer install
script:
- ./vendor/bin/phpunit -c ./tests/travis/phpunit.travis.xml -v
after_script:
- php vendor/bin/coveralls -v
matrix:
fast_finish: true
allow_failures:
- php: hhvm
- php: 7.0

19
core/vendor/doctrine/cache/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14
core/vendor/doctrine/cache/README.md vendored Normal file
View File

@@ -0,0 +1,14 @@
# Doctrine Cache
Master: [![Build Status](https://secure.travis-ci.org/doctrine/cache.png?branch=master)](http://travis-ci.org/doctrine/cache) [![Coverage Status](https://coveralls.io/repos/doctrine/cache/badge.png?branch=master)](https://coveralls.io/r/doctrine/cache?branch=master)
[![Latest Stable Version](https://poser.pugx.org/doctrine/cache/v/stable.png)](https://packagist.org/packages/doctrine/cache) [![Total Downloads](https://poser.pugx.org/doctrine/cache/downloads.png)](https://packagist.org/packages/doctrine/cache)
Cache component extracted from the Doctrine Common project.
## Changelog
### v1.2
* Added support for MongoDB as Cache Provider
* Fix namespace version reset

16
core/vendor/doctrine/cache/UPGRADE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
# Upgrade to 1.4
## Minor BC Break: `Doctrine\Common\Cache\FileCache#$extension` is now `private`.
If you need to override the value of `Doctrine\Common\Cache\FileCache#$extension`, then use the
second parameter of `Doctrine\Common\Cache\FileCache#__construct()` instead of overriding
the property in your own implementation.
## Minor BC Break: file based caches paths changed
`Doctrine\Common\Cache\FileCache`, `Doctrine\Common\Cache\PhpFileCache` and
`Doctrine\Common\Cache\FilesystemCache` are using a different cache paths structure.
If you rely on warmed up caches for deployments, consider that caches generated
with `doctrine/cache` `<1.4` are not compatible with the new directory structure,
and will be ignored.

View File

@@ -0,0 +1,3 @@
# Version class and file
project.version_class = Doctrine\\Common\\Cache\\Version
project.version_file = lib/Doctrine/Common/Cache/Version.php

110
core/vendor/doctrine/cache/build.xml vendored Normal file
View File

@@ -0,0 +1,110 @@
<?xml version="1.0"?>
<project name="DoctrineCommonCache" default="build" basedir=".">
<property file="build.properties" />
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<target name="prepare">
<mkdir dir="build" />
</target>
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="archive" />
<arg value="--dir=build" />
</exec>
</target>
<target name="composer" depends="php,composer-check,composer-download">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="install" />
</exec>
</target>
<target name="composer-check" depends="prepare">
<available file="build/composer.phar" property="composer.present"/>
</target>
<target name="composer-download" unless="composer.present">
<exec executable="wget">
<arg value="-Obuild/composer.phar" />
<arg value="http://getcomposer.org/composer.phar" />
</exec>
</target>
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
<exec executable="git" failonerror="true" outputproperty="current_git_branch">
<arg value="rev-parse" />
<arg value="--abbrev-ref" />
<arg value="HEAD" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
<arg value="-r" />
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
<arg value="-r" />
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
if (count($parts) != 3) {
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
}
if ('${current_git_branch}' === 'master') {
$parts[1]++;
} else {
$parts[2]++;
}
echo implode('.', $parts);
" />
</exec>
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
<git-tag version="${doctrine.current_version}" />
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
<arg value="--quiet" />
<arg value="HEAD" />
</exec>
</target>
<macrodef name="git-commit">
<attribute name="file" default="NOT SET"/>
<attribute name="message" default="NOT SET"/>
<sequential>
<exec executable="git">
<arg value="add" />
<arg value="@{file}" />
</exec>
<exec executable="git">
<arg value="commit" />
<arg value="-m" />
<arg value="@{message}" />
</exec>
</sequential>
</macrodef>
<macrodef name="git-tag">
<attribute name="version" default="NOT SET" />
<sequential>
<exec executable="git">
<arg value="tag" />
<arg value="-m" />
<arg value="v@{version}" />
<arg value="v@{version}" />
</exec>
</sequential>
</macrodef>
</project>

View File

@@ -0,0 +1,37 @@
{
"name": "doctrine/cache",
"type": "library",
"description": "Caching library offering an object-oriented API for many cache backends",
"keywords": ["cache", "caching"],
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": ">=3.7",
"satooshi/php-coveralls": "~0.6",
"predis/predis": "~1.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" }
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine/Tests" }
},
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* APC cache provider.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ApcCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return apc_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return apc_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return apc_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
// apc_delete returns false if the id does not exist
return apc_delete($id) || ! apc_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return apc_clear_cache() && apc_clear_cache('user');
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return apc_fetch($keys);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = apc_cache_info('', true);
$sma = apc_sma_info();
// @TODO - Temporary fix @see https://github.com/krakjoe/apcu/pull/42
if (PHP_VERSION_ID >= 50500) {
$info['num_hits'] = isset($info['num_hits']) ? $info['num_hits'] : $info['nhits'];
$info['num_misses'] = isset($info['num_misses']) ? $info['num_misses'] : $info['nmisses'];
$info['start_time'] = isset($info['start_time']) ? $info['start_time'] : $info['stime'];
}
return array(
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'],
);
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Array cache driver.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class ArrayCache extends CacheProvider
{
/**
* @var array $data
*/
private $data = array();
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->doContains($id) ? $this->data[$id] : false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
// isset() is required for performance optimizations, to avoid unnecessary function calls to array_key_exists.
return isset($this->data[$id]) || array_key_exists($id, $this->data);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = $data;
return true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
unset($this->data[$id]);
return true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->data = array();
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface Cache
{
const STATS_HITS = 'hits';
const STATS_MISSES = 'misses';
const STATS_UPTIME = 'uptime';
const STATS_MEMORY_USAGE = 'memory_usage';
const STATS_MEMORY_AVAILABLE = 'memory_available';
/**
* Only for backward compatibility (may be removed in next major release)
*
* @deprecated
*/
const STATS_MEMORY_AVAILIABLE = 'memory_available';
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
public function fetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
public function contains($id);
/**
* Puts data into the cache.
*
* If a cache entry with the given id already exists, its data will be replaced.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The lifetime in number of seconds for this cache entry.
* If zero (the default), the entry never expires (although it may be deleted from the cache
* to make place for other entries).
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function save($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
* Deleting a non-existing entry is considered successful.
*/
public function delete($id);
/**
* Retrieves cached information from the data store.
*
* The server's statistics array has the following values:
*
* - <b>hits</b>
* Number of keys that have been requested and found present.
*
* - <b>misses</b>
* Number of items that have been requested and not found.
*
* - <b>uptime</b>
* Time that the server is running.
*
* - <b>memory_usage</b>
* Memory used by this server to store items.
*
* - <b>memory_available</b>
* Memory allowed to use for storage.
*
* @since 2.2
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
public function getStats();
}

View File

@@ -0,0 +1,277 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Base class for cache provider implementations.
*
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiGetCache
{
const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
/**
* The namespace to prefix all cache ids with.
*
* @var string
*/
private $namespace = '';
/**
* The namespace version.
*
* @var integer|null
*/
private $namespaceVersion;
/**
* Sets the namespace to prefix all cache ids with.
*
* @param string $namespace
*
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = (string) $namespace;
$this->namespaceVersion = null;
}
/**
* Retrieves the namespace that prefixes all cache ids.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* {@inheritdoc}
*/
public function fetch($id)
{
return $this->doFetch($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function fetchMultiple(array $keys)
{
if (empty($keys)) {
return array();
}
// note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys
$namespacedKeys = array_combine($keys, array_map(array($this, 'getNamespacedId'), $keys));
$items = $this->doFetchMultiple($namespacedKeys);
$foundItems = array();
// no internal array function supports this sort of mapping: needs to be iterative
// this filters and combines keys in one pass
foreach ($namespacedKeys as $requestedKey => $namespacedKey) {
if (isset($items[$namespacedKey]) || array_key_exists($namespacedKey, $items)) {
$foundItems[$requestedKey] = $items[$namespacedKey];
}
}
return $foundItems;
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return $this->doContains($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function save($id, $data, $lifeTime = 0)
{
return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
return $this->doDelete($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function getStats()
{
return $this->doGetStats();
}
/**
* {@inheritDoc}
*/
public function flushAll()
{
return $this->doFlush();
}
/**
* {@inheritDoc}
*/
public function deleteAll()
{
$namespaceCacheKey = $this->getNamespaceCacheKey();
$namespaceVersion = $this->getNamespaceVersion() + 1;
if ($this->doSave($namespaceCacheKey, $namespaceVersion)) {
$this->namespaceVersion = $namespaceVersion;
return true;
}
return false;
}
/**
* Prefixes the passed id with the configured namespace value.
*
* @param string $id The id to namespace.
*
* @return string The namespaced id.
*/
private function getNamespacedId($id)
{
$namespaceVersion = $this->getNamespaceVersion();
return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
}
/**
* Returns the namespace cache key.
*
* @return string
*/
private function getNamespaceCacheKey()
{
return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
}
/**
* Returns the namespace version.
*
* @return integer
*/
private function getNamespaceVersion()
{
if (null !== $this->namespaceVersion) {
return $this->namespaceVersion;
}
$namespaceCacheKey = $this->getNamespaceCacheKey();
$this->namespaceVersion = $this->doFetch($namespaceCacheKey) ?: 1;
return $this->namespaceVersion;
}
/**
* Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it.
*
* @param array $keys Array of keys to retrieve from cache
* @return array Array of values retrieved for the given keys.
*/
protected function doFetchMultiple(array $keys)
{
$returnValues = array();
foreach ($keys as $key) {
if (false !== ($item = $this->doFetch($key)) || $this->doContains($key)) {
$returnValues[$key] = $item;
}
}
return $returnValues;
}
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed|false The cached data or FALSE, if no cache entry exists for the given id.
*/
abstract protected function doFetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
abstract protected function doContains($id);
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param string $data The cache entry/data.
* @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this
* cache entry (0 => infinite lifeTime).
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
abstract protected function doSave($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
abstract protected function doDelete($id);
/**
* Flushes all cache entries.
*
* @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
abstract protected function doFlush();
/**
* Retrieves cached information from the data store.
*
* @since 2.2
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
abstract protected function doGetStats();
}

View File

@@ -0,0 +1,147 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Cache provider that allows to easily chain multiple cache providers
*
* @author Michaël Gallego <mic.gallego@gmail.com>
*/
class ChainCache extends CacheProvider
{
/**
* @var CacheProvider[]
*/
private $cacheProviders = array();
/**
* Constructor
*
* @param CacheProvider[] $cacheProviders
*/
public function __construct($cacheProviders = array())
{
$this->cacheProviders = $cacheProviders;
}
/**
* {@inheritDoc}
*/
public function setNamespace($namespace)
{
parent::setNamespace($namespace);
foreach ($this->cacheProviders as $cacheProvider) {
$cacheProvider->setNamespace($namespace);
}
}
/**
* {@inheritDoc}
*/
protected function doFetch($id)
{
foreach ($this->cacheProviders as $key => $cacheProvider) {
if ($cacheProvider->doContains($id)) {
$value = $cacheProvider->doFetch($id);
// We populate all the previous cache layers (that are assumed to be faster)
for ($subKey = $key - 1 ; $subKey >= 0 ; $subKey--) {
$this->cacheProviders[$subKey]->doSave($id, $value);
}
return $value;
}
}
return false;
}
/**
* {@inheritDoc}
*/
protected function doContains($id)
{
foreach ($this->cacheProviders as $cacheProvider) {
if ($cacheProvider->doContains($id)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$stored = true;
foreach ($this->cacheProviders as $cacheProvider) {
$stored = $cacheProvider->doSave($id, $data, $lifeTime) && $stored;
}
return $stored;
}
/**
* {@inheritDoc}
*/
protected function doDelete($id)
{
$deleted = true;
foreach ($this->cacheProviders as $cacheProvider) {
$deleted = $cacheProvider->doDelete($id) && $deleted;
}
return $deleted;
}
/**
* {@inheritDoc}
*/
protected function doFlush()
{
$flushed = true;
foreach ($this->cacheProviders as $cacheProvider) {
$flushed = $cacheProvider->doFlush() && $flushed;
}
return $flushed;
}
/**
* {@inheritDoc}
*/
protected function doGetStats()
{
// We return all the stats from all adapters
$stats = array();
foreach ($this->cacheProviders as $cacheProvider) {
$stats[] = $cacheProvider->doGetStats();
}
return $stats;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache that can be flushed.
*
* Intended to be used for partial clearing of a cache namespace. For a more
* global "flushing", see {@see FlushableCache}.
*
* @link www.doctrine-project.org
* @since 1.4
* @author Adirelle <adirelle@gmail.com>
*/
interface ClearableCache
{
/**
* Deletes all cache entries in the current cache namespace.
*
* @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise.
*/
public function deleteAll();
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Couchbase;
/**
* Couchbase cache provider.
*
* @link www.doctrine-project.org
* @since 2.4
* @author Michael Nitschinger <michael@nitschinger.at>
*/
class CouchbaseCache extends CacheProvider
{
/**
* @var Couchbase|null
*/
private $couchbase;
/**
* Sets the Couchbase instance to use.
*
* @param Couchbase $couchbase
*
* @return void
*/
public function setCouchbase(Couchbase $couchbase)
{
$this->couchbase = $couchbase;
}
/**
* Gets the Couchbase instance used by the cache.
*
* @return Couchbase|null
*/
public function getCouchbase()
{
return $this->couchbase;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->couchbase->get($id) ?: false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (null !== $this->couchbase->get($id));
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->couchbase->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->couchbase->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->couchbase->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->couchbase->getStats();
$servers = $this->couchbase->getServers();
$server = explode(":", $servers[0]);
$key = $server[0] . ":" . "11210";
$stats = $stats[$key];
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
}
}

View File

@@ -0,0 +1,286 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Base file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Tobias Schultze <http://tobion.de>
*/
abstract class FileCache extends CacheProvider
{
/**
* The cache directory.
*
* @var string
*/
protected $directory;
/**
* The cache file extension.
*
* @var string
*/
private $extension;
/**
* @var int
*/
private $umask;
/**
* @var int
*/
private $directoryStringLength;
/**
* @var int
*/
private $extensionStringLength;
/**
* @var bool
*/
private $isRunningOnWindows;
/**
* Constructor.
*
* @param string $directory The cache directory.
* @param string $extension The cache file extension.
*
* @throws \InvalidArgumentException
*/
public function __construct($directory, $extension = '', $umask = 0002)
{
// YES, this needs to be *before* createPathIfNeeded()
if ( ! is_int($umask)) {
throw new \InvalidArgumentException(sprintf(
'The umask parameter is required to be integer, was: %s',
gettype($umask)
));
}
$this->umask = $umask;
if ( ! $this->createPathIfNeeded($directory)) {
throw new \InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$directory
));
}
if ( ! is_writable($directory)) {
throw new \InvalidArgumentException(sprintf(
'The directory "%s" is not writable.',
$directory
));
}
// YES, this needs to be *after* createPathIfNeeded()
$this->directory = realpath($directory);
$this->extension = (string) $extension;
$this->directoryStringLength = strlen($this->directory);
$this->extensionStringLength = strlen($this->extension);
$this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD');
}
/**
* Gets the cache directory.
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Gets the cache file extension.
*
* @return string
*/
public function getExtension()
{
return $this->extension;
}
/**
* @param string $id
*
* @return string
*/
protected function getFilename($id)
{
$hash = hash('sha256', $id);
// This ensures that the filename is unique and that there are no invalid chars in it.
if (
'' === $id
|| ((strlen($id) * 2 + $this->extensionStringLength) > 255)
|| ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258)
) {
// Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited
// to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API.
// And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259.
// So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents
// collisions between the hash and bin2hex.
$filename = '_' . $hash;
} else {
$filename = bin2hex($id);
}
return $this->directory
. DIRECTORY_SEPARATOR
. substr($hash, 0, 2)
. DIRECTORY_SEPARATOR
. $filename
. $this->extension;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
$filename = $this->getFilename($id);
return @unlink($filename) || ! file_exists($filename);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
foreach ($this->getIterator() as $name => $file) {
if ($file->isDir()) {
// Remove the intermediate directories which have been created to balance the tree. It only takes effect
// if the directory is empty. If several caches share the same directory but with different file extensions,
// the other ones are not removed.
@rmdir($name);
} elseif ($this->isFilenameEndingWithExtension($name)) {
// If an extension is set, only remove files which end with the given extension.
// If no extension is set, we have no other choice than removing everything.
@unlink($name);
}
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$usage = 0;
foreach ($this->getIterator() as $name => $file) {
if (! $file->isDir() && $this->isFilenameEndingWithExtension($name)) {
$usage += $file->getSize();
}
}
$free = disk_free_space($this->directory);
return array(
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $usage,
Cache::STATS_MEMORY_AVAILABLE => $free,
);
}
/**
* Create path if needed.
*
* @param string $path
* @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
*/
private function createPathIfNeeded($path)
{
if ( ! is_dir($path)) {
if (false === @mkdir($path, 0777 & (~$this->umask), true) && !is_dir($path)) {
return false;
}
}
return true;
}
/**
* Writes a string content to file in an atomic way.
*
* @param string $filename Path to the file where to write the data.
* @param string $content The content to write
*
* @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
*/
protected function writeFile($filename, $content)
{
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if ( ! $this->createPathIfNeeded($filepath)) {
return false;
}
if ( ! is_writable($filepath)) {
return false;
}
$tmpFile = tempnam($filepath, 'swap');
@chmod($tmpFile, 0666 & (~$this->umask));
if (file_put_contents($tmpFile, $content) !== false) {
if (@rename($tmpFile, $filename)) {
return true;
}
@unlink($tmpFile);
}
return false;
}
/**
* @return \Iterator
*/
private function getIterator()
{
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
}
/**
* @param string $name The filename
*
* @return bool
*/
private function isFilenameEndingWithExtension($name)
{
return '' === $this->extension
|| strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength);
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Filesystem cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class FilesystemCache extends FileCache
{
const EXTENSION = '.doctrinecache.data';
/**
* {@inheritdoc}
*/
public function __construct($directory, $extension = self::EXTENSION, $umask = 0002)
{
parent::__construct($directory, $extension, $umask);
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$data = '';
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
if (false !== ($line = fgets($resource))) {
$lifetime = (int) $line;
}
if ($lifetime !== 0 && $lifetime < time()) {
fclose($resource);
return false;
}
while (false !== ($line = fgets($resource))) {
$data .= $line;
}
fclose($resource);
return unserialize($data);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$lifetime = -1;
$filename = $this->getFilename($id);
if ( ! is_file($filename)) {
return false;
}
$resource = fopen($filename, "r");
if (false !== ($line = fgets($resource))) {
$lifetime = (int) $line;
}
fclose($resource);
return $lifetime === 0 || $lifetime > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
$data = serialize($data);
$filename = $this->getFilename($id);
return $this->writeFile($filename, $lifeTime . PHP_EOL . $data);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache that can be flushed.
*
* @link www.doctrine-project.org
* @since 1.4
* @author Adirelle <adirelle@gmail.com>
*/
interface FlushableCache
{
/**
* Flushes all cache entries, globally.
*
* @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
public function flushAll();
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcache;
/**
* Memcache cache provider.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcacheCache extends CacheProvider
{
/**
* @var Memcache|null
*/
private $memcache;
/**
* Sets the memcache instance to use.
*
* @param Memcache $memcache
*
* @return void
*/
public function setMemcache(Memcache $memcache)
{
$this->memcache = $memcache;
}
/**
* Gets the memcache instance used by the cache.
*
* @return Memcache|null
*/
public function getMemcache()
{
return $this->memcache;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcache->get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$flags = null;
$this->memcache->get($id, $flags);
//if memcache has changed the value of "flags", it means the value exists
return ($flags !== null);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcache->set($id, $data, 0, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
// Memcache::delete() returns false if entry does not exist
return $this->memcache->delete($id) || ! $this->doContains($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcache->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcache->getStats();
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use \Memcached;
/**
* Memcached cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class MemcachedCache extends CacheProvider
{
/**
* @var Memcached|null
*/
private $memcached;
/**
* Sets the memcache instance to use.
*
* @param Memcached $memcached
*
* @return void
*/
public function setMemcached(Memcached $memcached)
{
$this->memcached = $memcached;
}
/**
* Gets the memcached instance used by the cache.
*
* @return Memcached|null
*/
public function getMemcached()
{
return $this->memcached;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcached->get($id);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return $this->memcached->getMulti($keys);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return false !== $this->memcached->get($id)
|| $this->memcached->getResultCode() !== Memcached::RES_NOTFOUND;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcached->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->memcached->delete($id)
|| $this->memcached->getResultCode() === Memcached::RES_NOTFOUND;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcached->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcached->getStats();
$servers = $this->memcached->getServerList();
$key = $servers[0]['host'] . ':' . $servers[0]['port'];
$stats = $stats[$key];
return array(
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
);
}
}

View File

@@ -0,0 +1,197 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use MongoBinData;
use MongoCollection;
use MongoCursorException;
use MongoDate;
/**
* MongoDB cache provider.
*
* @since 1.1
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class MongoDBCache extends CacheProvider
{
/**
* The data field will store the serialized PHP value.
*/
const DATA_FIELD = 'd';
/**
* The expiration field will store a MongoDate value indicating when the
* cache entry should expire.
*
* With MongoDB 2.2+, entries can be automatically deleted by MongoDB by
* indexing this field with the "expireAfterSeconds" option equal to zero.
* This will direct MongoDB to regularly query for and delete any entries
* whose date is older than the current time. Entries without a date value
* in this field will be ignored.
*
* The cache provider will also check dates on its own, in case expired
* entries are fetched before MongoDB's TTLMonitor pass can expire them.
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
*/
const EXPIRATION_FIELD = 'e';
/**
* @var MongoCollection
*/
private $collection;
/**
* Constructor.
*
* This provider will default to the write concern and read preference
* options set on the MongoCollection instance (or inherited from MongoDB or
* MongoClient). Using an unacknowledged write concern (< 1) may make the
* return values of delete() and save() unreliable. Reading from secondaries
* may make contain() and fetch() unreliable.
*
* @see http://www.php.net/manual/en/mongo.readpreferences.php
* @see http://www.php.net/manual/en/mongo.writeconcerns.php
* @param MongoCollection $collection
*/
public function __construct(MongoCollection $collection)
{
$this->collection = $collection;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$document = $this->collection->findOne(array('_id' => $id), array(self::DATA_FIELD, self::EXPIRATION_FIELD));
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->doDelete($id);
return false;
}
return unserialize($document[self::DATA_FIELD]->bin);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$document = $this->collection->findOne(array('_id' => $id), array(self::EXPIRATION_FIELD));
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->doDelete($id);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
try {
$result = $this->collection->update(
array('_id' => $id),
array('$set' => array(
self::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
self::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
)),
array('upsert' => true, 'multiple' => false)
);
} catch (MongoCursorException $e) {
return false;
}
return isset($result['ok']) ? $result['ok'] == 1 : true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
$result = $this->collection->remove(array('_id' => $id));
return isset($result['ok']) ? $result['ok'] == 1 : true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
// Use remove() in lieu of drop() to maintain any collection indexes
$result = $this->collection->remove();
return isset($result['ok']) ? $result['ok'] == 1 : true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$serverStatus = $this->collection->db->command(array(
'serverStatus' => 1,
'locks' => 0,
'metrics' => 0,
'recordStats' => 0,
'repl' => 0,
));
$collStats = $this->collection->db->command(array('collStats' => 1));
return array(
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => (isset($serverStatus['uptime']) ? (int) $serverStatus['uptime'] : null),
Cache::STATS_MEMORY_USAGE => (isset($collStats['size']) ? (int) $collStats['size'] : null),
Cache::STATS_MEMORY_AVAILABLE => null,
);
}
/**
* Check if the document is expired.
*
* @param array $document
*
* @return bool
*/
private function isExpired(array $document)
{
return isset($document[self::EXPIRATION_FIELD]) &&
$document[self::EXPIRATION_FIELD] instanceof MongoDate &&
$document[self::EXPIRATION_FIELD]->sec < time();
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to get many items at once.
*
* @link www.doctrine-project.org
* @since 1.4
* @author Asmir Mustafic <goetas@gmail.com>
*/
interface MultiGetCache
{
/**
* Returns an associative array of values for keys is found in cache.
*
* @param string[] $keys Array of keys to retrieve from cache
* @return mixed[] Array of retrieved values, indexed by the specified keys.
* Values that couldn't be retrieved are not contained in this array.
*/
function fetchMultiple(array $keys);
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Php file cache driver.
*
* @since 2.3
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class PhpFileCache extends FileCache
{
const EXTENSION = '.doctrinecache.php';
/**
* {@inheritdoc}
*/
public function __construct($directory, $extension = self::EXTENSION, $umask = 0002)
{
parent::__construct($directory, $extension, $umask);
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$value = $this->includeFileForId($id);
if (! $value) {
return false;
}
if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) {
return false;
}
return $value['data'];
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$value = $this->includeFileForId($id);
if (! $value) {
return false;
}
return $value['lifetime'] === 0 || $value['lifetime'] > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
if (is_object($data) && ! method_exists($data, '__set_state')) {
throw new \InvalidArgumentException(
"Invalid argument given, PhpFileCache only allows objects that implement __set_state() " .
"and fully support var_export(). You can use the FilesystemCache to save arbitrary object " .
"graphs using serialize()/deserialize()."
);
}
$filename = $this->getFilename($id);
$value = array(
'lifetime' => $lifeTime,
'data' => $data
);
$value = var_export($value, true);
$code = sprintf('<?php return %s;', $value);
return $this->writeFile($filename, $code);
}
/**
* @param string $id
*
* @return array|false
*/
private function includeFileForId($id)
{
$fileName = $this->getFilename($id);
// note: error suppression is still faster than `file_exists`, `is_file` and `is_readable`
$value = @include $fileName;
if (! isset($value['lifetime'])) {
return false;
}
return $value;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Doctrine\Common\Cache;
use Predis\ClientInterface;
/**
* Predis cache provider.
*
* @author othillo <othillo@othillo.nl>
*/
class PredisCache extends CacheProvider
{
/**
* @var ClientInterface
*/
private $client;
/**
* @param ClientInterface $client
*
* @return void
*/
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$result = $this->client->get($id);
if (null === $result) {
return false;
}
return unserialize($result);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
$fetchedItems = call_user_func_array(array($this->client, 'mget'), $keys);
return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems)));
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->client->exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$data = serialize($data);
if ($lifeTime > 0) {
$response = $this->client->setex($id, $lifeTime, $data);
} else {
$response = $this->client->set($id, $data);
}
return $response === true || $response == 'OK';
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->client->del($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$response = $this->client->flushdb();
return $response === true || $response == 'OK';
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = $this->client->info();
return array(
Cache::STATS_HITS => $info['Stats']['keyspace_hits'],
Cache::STATS_MISSES => $info['Stats']['keyspace_misses'],
Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false
);
}
}

View File

@@ -0,0 +1,153 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use Redis;
/**
* Redis cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Osman Ungur <osmanungur@gmail.com>
*/
class RedisCache extends CacheProvider
{
/**
* @var Redis|null
*/
private $redis;
/**
* Sets the redis instance to use.
*
* @param Redis $redis
*
* @return void
*/
public function setRedis(Redis $redis)
{
$redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue());
$this->redis = $redis;
}
/**
* Gets the redis instance used by the cache.
*
* @return Redis|null
*/
public function getRedis()
{
return $this->redis;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->redis->get($id);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
$fetchedItems = array_combine($keys, $this->redis->mget($keys));
// Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data.
$foundItems = array();
foreach ($fetchedItems as $key => $value) {
if (false !== $value || $this->redis->exists($key)) {
$foundItems[$key] = $value;
}
}
return $foundItems;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->redis->exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
return $this->redis->setex($id, $lifeTime, $data);
}
return $this->redis->set($id, $data);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->redis->delete($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->redis->flushDB();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = $this->redis->info();
return array(
Cache::STATS_HITS => $info['keyspace_hits'],
Cache::STATS_MISSES => $info['keyspace_misses'],
Cache::STATS_UPTIME => $info['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false
);
}
/**
* Returns the serializer constant to use. If Redis is compiled with
* igbinary support, that is used. Otherwise the default PHP serializer is
* used.
*
* @return integer One of the Redis::SERIALIZER_* constants
*/
protected function getSerializerValue()
{
if (defined('HHVM_VERSION')) {
return Redis::SERIALIZER_PHP;
}
return defined('Redis::SERIALIZER_IGBINARY') ? Redis::SERIALIZER_IGBINARY : Redis::SERIALIZER_PHP;
}
}

View File

@@ -0,0 +1,250 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use Riak\Bucket;
use Riak\Connection;
use Riak\Input;
use Riak\Exception;
use Riak\Object;
/**
* Riak cache provider.
*
* @link www.doctrine-project.org
* @since 1.1
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class RiakCache extends CacheProvider
{
const EXPIRES_HEADER = 'X-Riak-Meta-Expires';
/**
* @var \Riak\Bucket
*/
private $bucket;
/**
* Sets the riak bucket instance to use.
*
* @param \Riak\Bucket $bucket
*/
public function __construct(Bucket $bucket)
{
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
try {
$response = $this->bucket->get($id);
// No objects found
if ( ! $response->hasObject()) {
return false;
}
// Check for attempted siblings
$object = ($response->hasSiblings())
? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList())
: $response->getFirstObject();
// Check for expired object
if ($this->isExpired($object)) {
$this->bucket->delete($object);
return false;
}
return unserialize($object->getContent());
} catch (Exception\RiakException $e) {
// Covers:
// - Riak\ConnectionException
// - Riak\CommunicationException
// - Riak\UnexpectedResponseException
// - Riak\NotFoundException
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
try {
// We only need the HEAD, not the entire object
$input = new Input\GetInput();
$input->setReturnHead(true);
$response = $this->bucket->get($id, $input);
// No objects found
if ( ! $response->hasObject()) {
return false;
}
$object = $response->getFirstObject();
// Check for expired object
if ($this->isExpired($object)) {
$this->bucket->delete($object);
return false;
}
return true;
} catch (Exception\RiakException $e) {
// Do nothing
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
try {
$object = new Object($id);
$object->setContent(serialize($data));
if ($lifeTime > 0) {
$object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime));
}
$this->bucket->put($object);
return true;
} catch (Exception\RiakException $e) {
// Do nothing
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
try {
$this->bucket->delete($id);
return true;
} catch (Exception\BadArgumentsException $e) {
// Key did not exist on cluster already
} catch (Exception\RiakException $e) {
// Covers:
// - Riak\Exception\ConnectionException
// - Riak\Exception\CommunicationException
// - Riak\Exception\UnexpectedResponseException
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
try {
$keyList = $this->bucket->getKeyList();
foreach ($keyList as $key) {
$this->bucket->delete($key);
}
return true;
} catch (Exception\RiakException $e) {
// Do nothing
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
// Only exposed through HTTP stats API, not Protocol Buffers API
return null;
}
/**
* Check if a given Riak Object have expired.
*
* @param \Riak\Object $object
*
* @return bool
*/
private function isExpired(Object $object)
{
$metadataMap = $object->getMetadataMap();
return isset($metadataMap[self::EXPIRES_HEADER])
&& $metadataMap[self::EXPIRES_HEADER] < time();
}
/**
* On-read conflict resolution. Applied approach here is last write wins.
* Specific needs may override this method to apply alternate conflict resolutions.
*
* {@internal Riak does not attempt to resolve a write conflict, and store
* it as sibling of conflicted one. By following this approach, it is up to
* the next read to resolve the conflict. When this happens, your fetched
* object will have a list of siblings (read as a list of objects).
* In our specific case, we do not care about the intermediate ones since
* they are all the same read from storage, and we do apply a last sibling
* (last write) wins logic.
* If by any means our resolution generates another conflict, it'll up to
* next read to properly solve it.}
*
* @param string $id
* @param string $vClock
* @param array $objectList
*
* @return \Riak\Object
*/
protected function resolveConflict($id, $vClock, array $objectList)
{
// Our approach here is last-write wins
$winner = $objectList[count($objectList)];
$putInput = new Input\PutInput();
$putInput->setVClock($vClock);
$mergedObject = new Object($id);
$mergedObject->setContent($winner->getContent());
$this->bucket->put($mergedObject, $putInput);
return $mergedObject;
}
}

View File

@@ -0,0 +1,220 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
use SQLite3;
use SQLite3Result;
/**
* SQLite3 cache provider.
*
* @since 1.4
* @author Jake Bell <jake@theunraveler.com>
*/
class SQLite3Cache extends CacheProvider
{
/**
* The ID field will store the cache key.
*/
const ID_FIELD = 'k';
/**
* The data field will store the serialized PHP value.
*/
const DATA_FIELD = 'd';
/**
* The expiration field will store a date value indicating when the
* cache entry should expire.
*/
const EXPIRATION_FIELD = 'e';
/**
* @var SQLite3
*/
private $sqlite;
/**
* @var string
*/
private $table;
/**
* Constructor.
*
* Calling the constructor will ensure that the database file and table
* exist and will create both if they don't.
*
* @param SQLite3 $sqlite
* @param string $table
*/
public function __construct(SQLite3 $sqlite, $table)
{
$this->sqlite = $sqlite;
$this->table = (string) $table;
list($id, $data, $exp) = $this->getFields();
return $this->sqlite->exec(sprintf(
'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)',
$table,
$id,
$data,
$exp
));
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
if ($item = $this->findById($id)) {
return unserialize($item[self::DATA_FIELD]);
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return null !== $this->findById($id, false);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$statement = $this->sqlite->prepare(sprintf(
'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)',
$this->table,
implode(',', $this->getFields())
));
$statement->bindValue(':id', $id);
$statement->bindValue(':data', serialize($data), SQLITE3_BLOB);
$statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null);
return $statement->execute() instanceof SQLite3Result;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
list($idField) = $this->getFields();
$statement = $this->sqlite->prepare(sprintf(
'DELETE FROM %s WHERE %s = :id',
$this->table,
$idField
));
$statement->bindValue(':id', $id);
return $statement->execute() instanceof SQLite3Result;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table));
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
// no-op.
}
/**
* Find a single row by ID.
*
* @param mixed $id
* @param bool $includeData
*
* @return array|null
*/
private function findById($id, $includeData = true)
{
list($idField) = $fields = $this->getFields();
if (!$includeData) {
$key = array_search(static::DATA_FIELD, $fields);
unset($fields[$key]);
}
$statement = $this->sqlite->prepare(sprintf(
'SELECT %s FROM %s WHERE %s = :id LIMIT 1',
implode(',', $fields),
$this->table,
$idField
));
$statement->bindValue(':id', $id, SQLITE3_TEXT);
$item = $statement->execute()->fetchArray(SQLITE3_ASSOC);
if ($item === false) {
return null;
}
if ($this->isExpired($item)) {
$this->doDelete($id);
return null;
}
return $item;
}
/**
* Gets an array of the fields in our table.
*
* @return array
*/
private function getFields()
{
return array(static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD);
}
/**
* Check if the item is expired.
*
* @param array $item
*
* @return bool
*/
private function isExpired(array $item)
{
return isset($item[static::EXPIRATION_FIELD]) &&
$item[self::EXPIRATION_FIELD] !== null &&
$item[self::EXPIRATION_FIELD] < time();
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
class Version
{
const VERSION = '1.5.4';
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Void cache driver. The cache could be of use in tests where you don`t need to cache anything.
*
* @link www.doctrine-project.org
* @since 1.5
* @author Kotlyar Maksim <kotlyar.maksim@gmail.com>
*/
class VoidCache extends CacheProvider
{
/**
* {@inheritDoc}
*/
protected function doFetch($id)
{
return false;
}
/**
* {@inheritDoc}
*/
protected function doContains($id)
{
return false;
}
/**
* {@inheritDoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doDelete($id)
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doFlush()
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doGetStats()
{
return;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* WinCache cache provider.
*
* @link www.doctrine-project.org
* @since 2.2
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class WinCacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return wincache_ucache_get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return wincache_ucache_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return wincache_ucache_set($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return wincache_ucache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return wincache_ucache_clear();
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return wincache_ucache_get($keys);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = wincache_ucache_info();
$meminfo = wincache_ucache_meminfo();
return array(
Cache::STATS_HITS => $info['total_hit_count'],
Cache::STATS_MISSES => $info['total_miss_count'],
Cache::STATS_UPTIME => $info['total_cache_uptime'],
Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'],
Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'],
);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Xcache cache driver.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author David Abdemoulaie <dave@hobodave.com>
*/
class XcacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->doContains($id) ? unserialize(xcache_get($id)) : false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return xcache_isset($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return xcache_set($id, serialize($data), (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return xcache_unset($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->checkAuthorization();
xcache_clear_cache(XC_TYPE_VAR);
return true;
}
/**
* Checks that xcache.admin.enable_auth is Off.
*
* @return void
*
* @throws \BadMethodCallException When xcache.admin.enable_auth is On.
*/
protected function checkAuthorization()
{
if (ini_get('xcache.admin.enable_auth')) {
throw new \BadMethodCallException(
'To use all features of \Doctrine\Common\Cache\XcacheCache, '
. 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'
);
}
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$this->checkAuthorization();
$info = xcache_info(XC_TYPE_VAR, 0);
return array(
Cache::STATS_HITS => $info['hits'],
Cache::STATS_MISSES => $info['misses'],
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $info['size'],
Cache::STATS_MEMORY_AVAILABLE => $info['avail'],
);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Cache;
/**
* Zend Data Cache cache driver.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Ralph Schindler <ralph.schindler@zend.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class ZendDataCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return zend_shm_cache_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (false !== zend_shm_cache_fetch($id));
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return zend_shm_cache_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return zend_shm_cache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$namespace = $this->getNamespace();
if (empty($namespace)) {
return zend_shm_cache_clear();
}
return zend_shm_cache_clear($namespace);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Doctrine Cache Test Suite">
<directory>./tests/Doctrine/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./lib/Doctrine/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,21 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\ApcCache;
/**
* @requires extension apc
*/
class ApcCacheTest extends CacheTest
{
protected function _getCacheDriver()
{
return new ApcCache();
}
public function testLifetime()
{
$this->markTestSkipped('The APC cache TTL is not working in a single process/request. See https://bugs.php.net/bug.php?id=58084');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\ArrayCache;
class ArrayCacheTest extends CacheTest
{
protected function _getCacheDriver()
{
return new ArrayCache();
}
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats);
}
public function testLifetime()
{
$this->markTestSkipped('ArrayCache does not implement TTL currently.');
}
protected function isSharedStorage()
{
return false;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
abstract class BaseFileCacheTest extends CacheTest
{
protected $directory;
protected function setUp()
{
do {
$this->directory = sys_get_temp_dir() . '/doctrine_cache_'. uniqid();
} while (file_exists($this->directory));
}
protected function tearDown()
{
if ( ! is_dir($this->directory)) {
return;
}
$iterator = new RecursiveDirectoryIterator($this->directory);
foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) {
if ($file->isFile()) {
@unlink($file->getRealPath());
} elseif ($file->isDir()) {
@rmdir($file->getRealPath());
}
}
@rmdir($this->directory);
}
public function testFlushAllRemovesBalancingDirectories()
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save('key1', 1));
$this->assertTrue($cache->save('key2', 2));
$this->assertTrue($cache->flushAll());
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
$this->assertCount(0, $iterator);
}
protected function isSharedStorage()
{
return false;
}
public function getPathLengthsToTest()
{
// Windows officially supports 260 bytes including null terminator
// 258 bytes available to use due to php bug #70943
// Windows officially supports 260 bytes including null terminator
// 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943)
// 260 characters is too large - null terminator is included in allowable length
return array(
array(257, false),
array(258, false),
array(259, true),
array(260, true)
);
}
private static function getBasePathForWindowsPathLengthTests($pathLength)
{
return FileCacheTest::getBasePathForWindowsPathLengthTests($pathLength);
}
private static function getKeyAndPathFittingLength($length)
{
$basePath = self::getBasePathForWindowsPathLengthTests($length);
$baseDirLength = strlen($basePath);
$extensionLength = strlen('.doctrine.cache');
$directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR);
$namespaceAndBracketLength = strlen(bin2hex("[][1]"));
$keyLength = $length
- ($baseDirLength
+ $extensionLength
+ $directoryLength
+ $namespaceAndBracketLength);
$key = str_repeat('a', floor($keyLength / 2));
$namespacedKey = '[' . $key . '][1]';
$keyHash = hash('sha256', $namespacedKey);
$keyPath = $basePath
. DIRECTORY_SEPARATOR
. substr($keyHash, 0, 2)
. DIRECTORY_SEPARATOR
. bin2hex($namespacedKey)
. '.doctrine.cache';
$hashedKeyPath = $basePath
. DIRECTORY_SEPARATOR
. substr($keyHash, 0, 2)
. DIRECTORY_SEPARATOR
. '_' . $keyHash
. '.doctrine.cache';
return array($key, $keyPath, $hashedKeyPath);
}
/**
* @dataProvider getPathLengthsToTest
*/
public function testWindowsPathLengthLimitIsCorrectlyHandled($length, $pathShouldBeHashed)
{
$this->directory = self::getBasePathForWindowsPathLengthTests($length);
list($key, $keyPath, $hashedKeyPath) = self::getKeyAndPathFittingLength($length);
$this->assertEquals($length, strlen($keyPath), "Unhashed path should be of correct length.");
$cacheClass = get_class($this->_getCacheDriver());
$cache = new $cacheClass($this->directory, '.doctrine.cache');
// Trick it into thinking this is windows.
$reflClass = new \ReflectionClass('\Doctrine\Common\Cache\FileCache');
$reflProp = $reflClass->getProperty('isRunningOnWindows');
$reflProp->setAccessible(true);
$reflProp->setValue($cache, true);
$reflProp->setAccessible(false);
$cache->save($key, $length);
$fetched = $cache->fetch($key);
$this->assertEquals($length, $fetched);
if ($pathShouldBeHashed) {
$this->assertFileExists($hashedKeyPath, "Path generated for key should be hashed.");
unlink($hashedKeyPath);
} else {
$this->assertFileExists($keyPath, "Path generated for key should not be hashed.");
unlink($keyPath);
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Doctrine\Tests\Common\Cache;
class CacheProviderTest extends \Doctrine\Tests\DoctrineTestCase
{
public function testFetchMultiWillFilterNonRequestedKeys()
{
/* @var $cache \Doctrine\Common\Cache\CacheProvider|\PHPUnit_Framework_MockObject_MockObject */
$cache = $this->getMockForAbstractClass(
'Doctrine\Common\Cache\CacheProvider',
array(),
'',
true,
true,
true,
array('doFetchMultiple')
);
$cache
->expects($this->once())
->method('doFetchMultiple')
->will($this->returnValue(array(
'[foo][1]' => 'bar',
'[bar][1]' => 'baz',
'[baz][1]' => 'tab',
)));
$this->assertEquals(
array('foo' => 'bar', 'bar' => 'baz'),
$cache->fetchMultiple(array('foo', 'bar'))
);
}
public function testFailedDeleteAllDoesNotChangeNamespaceVersion()
{
/* @var $cache \Doctrine\Common\Cache\CacheProvider|\PHPUnit_Framework_MockObject_MockObject */
$cache = $this->getMockForAbstractClass(
'Doctrine\Common\Cache\CacheProvider',
array(),
'',
true,
true,
true,
array('doFetch', 'doSave', 'doContains')
);
$cache
->expects($this->once())
->method('doFetch')
->with('DoctrineNamespaceCacheKey[]')
->will($this->returnValue(false));
// doSave is only called once from deleteAll as we do not need to persist the default version in getNamespaceVersion()
$cache
->expects($this->once())
->method('doSave')
->with('DoctrineNamespaceCacheKey[]')
->will($this->returnValue(false));
// After a failed deleteAll() the local namespace version is not increased (still 1). Otherwise all data written afterwards
// would be lost outside the current instance.
$cache
->expects($this->once())
->method('doContains')
->with('[key][1]')
->will($this->returnValue(true));
$this->assertFalse($cache->deleteAll(), 'deleteAll() returns false when saving the namespace version fails');
$cache->contains('key');
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use ArrayObject;
abstract class CacheTest extends \Doctrine\Tests\DoctrineTestCase
{
/**
* @dataProvider provideDataToCache
*/
public function testSetContainsFetchDelete($value)
{
$cache = $this->_getCacheDriver();
// Test saving a value, checking if it exists, and fetching it back
$this->assertTrue($cache->save('key', $value));
$this->assertTrue($cache->contains('key'));
if (is_object($value)) {
$this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
} else {
$this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
}
// Test deleting a value
$this->assertTrue($cache->delete('key'));
$this->assertFalse($cache->contains('key'));
$this->assertFalse($cache->fetch('key'));
}
/**
* @dataProvider provideDataToCache
*/
public function testUpdateExistingEntry($value)
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save('key', 'old-value'));
$this->assertTrue($cache->contains('key'));
$this->assertTrue($cache->save('key', $value));
$this->assertTrue($cache->contains('key'));
if (is_object($value)) {
$this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
} else {
$this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
}
}
public function testCacheKeyIsCaseSensitive()
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save('key', 'value'));
$this->assertTrue($cache->contains('key'));
$this->assertSame('value', $cache->fetch('key'));
$this->assertFalse($cache->contains('KEY'));
$this->assertFalse($cache->fetch('KEY'));
$cache->delete('KEY');
$this->assertTrue($cache->contains('key', 'Deleting cache item with different case must not affect other cache item'));
}
public function testFetchMultiple()
{
$cache = $this->_getCacheDriver();
$values = $this->provideDataToCache();
$saved = array();
foreach ($values as $key => $value) {
$cache->save($key, $value[0]);
$saved[$key] = $value[0];
}
$keys = array_keys($saved);
$this->assertEquals(
$saved,
$cache->fetchMultiple($keys),
'Testing fetchMultiple with different data types'
);
$this->assertEquals(
array_slice($saved, 0, 1),
$cache->fetchMultiple(array_slice($keys, 0, 1)),
'Testing fetchMultiple with a single key'
);
$keysWithNonExisting = array();
$keysWithNonExisting[] = 'non_existing1';
$keysWithNonExisting[] = $keys[0];
$keysWithNonExisting[] = 'non_existing2';
$keysWithNonExisting[] = $keys[1];
$keysWithNonExisting[] = 'non_existing3';
$this->assertEquals(
array_slice($saved, 0, 2),
$cache->fetchMultiple($keysWithNonExisting),
'Testing fetchMultiple with a subset of keys and mixed with non-existing ones'
);
}
public function testFetchMultipleWithNoKeys()
{
$cache = $this->_getCacheDriver();
$this->assertSame(array(), $cache->fetchMultiple(array()));
}
public function provideDataToCache()
{
$obj = new \stdClass();
$obj->foo = 'bar';
$obj2 = new \stdClass();
$obj2->bar = 'foo';
$obj2->obj = $obj;
$obj->obj2 = $obj2;
return array(
'array' => array(array('one', 2, 3.01)),
'string' => array('value'),
'string_invalid_utf8' => array("\xc3\x28"),
'string_null_byte' => array('with'."\0".'null char'),
'integer' => array(1),
'float' => array(1.5),
'object' => array(new ArrayObject(array('one', 2, 3.01))),
'object_recursive' => array($obj),
'true' => array(true),
// the following are considered FALSE in boolean context, but caches should still recognize their existence
'null' => array(null),
'false' => array(false),
'array_empty' => array(array()),
'string_zero' => array('0'),
'integer_zero' => array(0),
'float_zero' => array(0.0),
'string_empty' => array(''),
);
}
public function testDeleteIsSuccessfulWhenKeyDoesNotExist()
{
$cache = $this->_getCacheDriver();
$cache->delete('key');
$this->assertFalse($cache->contains('key'));
$this->assertTrue($cache->delete('key'));
}
public function testDeleteAll()
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save('key1', 1));
$this->assertTrue($cache->save('key2', 2));
$this->assertTrue($cache->deleteAll());
$this->assertFalse($cache->contains('key1'));
$this->assertFalse($cache->contains('key2'));
}
/**
* @dataProvider provideCacheIds
*/
public function testCanHandleSpecialCacheIds($id)
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save($id, 'value'));
$this->assertTrue($cache->contains($id));
$this->assertEquals('value', $cache->fetch($id));
$this->assertTrue($cache->delete($id));
$this->assertFalse($cache->contains($id));
$this->assertFalse($cache->fetch($id));
}
public function testNoCacheIdCollisions()
{
$cache = $this->_getCacheDriver();
$ids = $this->provideCacheIds();
// fill cache with each id having a different value
foreach ($ids as $index => $id) {
$cache->save($id[0], $index);
}
// then check value of each cache id
foreach ($ids as $index => $id) {
$value = $cache->fetch($id[0]);
$this->assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0]));
if ($index !== $value) {
$this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0]));
}
}
}
/**
* Returns cache ids with special characters that should still work.
*
* For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly.
* Each cache id should be considered different from the others.
*
* @return array
*/
public function provideCacheIds()
{
return array(
array(':'),
array('\\'),
array('/'),
array('<'),
array('>'),
array('"'),
array('*'),
array('?'),
array('|'),
array('['),
array(']'),
array('ä'),
array('a'),
array('é'),
array('e'),
array('.'), // directory traversal
array('..'), // directory traversal
array('-'),
array('_'),
array('$'),
array('%'),
array(' '),
array("\0"),
array(''),
array(str_repeat('a', 300)), // long key
array(str_repeat('a', 113)),
);
}
public function testLifetime()
{
$cache = $this->_getCacheDriver();
$cache->save('expire', 'value', 1);
$this->assertTrue($cache->contains('expire'), 'Data should not be expired yet');
// @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
sleep(2);
$this->assertFalse($cache->contains('expire'), 'Data should be expired');
}
public function testNoExpire()
{
$cache = $this->_getCacheDriver();
$cache->save('noexpire', 'value', 0);
// @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
sleep(1);
$this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire');
}
public function testLongLifetime()
{
$cache = $this->_getCacheDriver();
$cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1);
$this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted');
}
public function testDeleteAllAndNamespaceVersioningBetweenCaches()
{
if ( ! $this->isSharedStorage()) {
$this->markTestSkipped('The cache storage needs to be shared.');
}
$cache1 = $this->_getCacheDriver();
$cache2 = $this->_getCacheDriver();
$this->assertTrue($cache1->save('key1', 1));
$this->assertTrue($cache2->save('key2', 2));
/* Both providers are initialized with the same namespace version, so
* they can see entries set by each other.
*/
$this->assertTrue($cache1->contains('key1'));
$this->assertTrue($cache1->contains('key2'));
$this->assertTrue($cache2->contains('key1'));
$this->assertTrue($cache2->contains('key2'));
/* Deleting all entries through one provider will only increment the
* namespace version on that object (and in the cache itself, which new
* instances will use to initialize). The second provider will retain
* its original version and still see stale data.
*/
$this->assertTrue($cache1->deleteAll());
$this->assertFalse($cache1->contains('key1'));
$this->assertFalse($cache1->contains('key2'));
$this->assertTrue($cache2->contains('key1'));
$this->assertTrue($cache2->contains('key2'));
/* A new cache provider should not see the deleted entries, since its
* namespace version will be initialized.
*/
$cache3 = $this->_getCacheDriver();
$this->assertFalse($cache3->contains('key1'));
$this->assertFalse($cache3->contains('key2'));
}
public function testFlushAll()
{
$cache = $this->_getCacheDriver();
$this->assertTrue($cache->save('key1', 1));
$this->assertTrue($cache->save('key2', 2));
$this->assertTrue($cache->flushAll());
$this->assertFalse($cache->contains('key1'));
$this->assertFalse($cache->contains('key2'));
}
public function testFlushAllAndNamespaceVersioningBetweenCaches()
{
if ( ! $this->isSharedStorage()) {
$this->markTestSkipped('The cache storage needs to be shared.');
}
$cache1 = $this->_getCacheDriver();
$cache2 = $this->_getCacheDriver();
/* Deleting all elements from the first provider should increment its
* namespace version before saving the first entry.
*/
$cache1->deleteAll();
$this->assertTrue($cache1->save('key1', 1));
/* The second provider will be initialized with the same namespace
* version upon its first save operation.
*/
$this->assertTrue($cache2->save('key2', 2));
/* Both providers have the same namespace version and can see entries
* set by each other.
*/
$this->assertTrue($cache1->contains('key1'));
$this->assertTrue($cache1->contains('key2'));
$this->assertTrue($cache2->contains('key1'));
$this->assertTrue($cache2->contains('key2'));
/* Flushing all entries through one cache will remove all entries from
* the cache but leave their namespace version as-is.
*/
$this->assertTrue($cache1->flushAll());
$this->assertFalse($cache1->contains('key1'));
$this->assertFalse($cache1->contains('key2'));
$this->assertFalse($cache2->contains('key1'));
$this->assertFalse($cache2->contains('key2'));
/* Inserting a new entry will use the same, incremented namespace
* version, and it will be visible to both providers.
*/
$this->assertTrue($cache1->save('key1', 1));
$this->assertTrue($cache1->contains('key1'));
$this->assertTrue($cache2->contains('key1'));
/* A new cache provider will be initialized with the original namespace
* version and not share any visibility with the first two providers.
*/
$cache3 = $this->_getCacheDriver();
$this->assertFalse($cache3->contains('key1'));
$this->assertFalse($cache3->contains('key2'));
$this->assertTrue($cache3->save('key3', 3));
$this->assertTrue($cache3->contains('key3'));
}
public function testNamespace()
{
$cache = $this->_getCacheDriver();
$cache->setNamespace('ns1_');
$this->assertTrue($cache->save('key1', 1));
$this->assertTrue($cache->contains('key1'));
$cache->setNamespace('ns2_');
$this->assertFalse($cache->contains('key1'));
}
public function testDeleteAllNamespace()
{
$cache = $this->_getCacheDriver();
$cache->setNamespace('ns1');
$this->assertFalse($cache->contains('key1'));
$cache->save('key1', 'test');
$this->assertTrue($cache->contains('key1'));
$cache->setNamespace('ns2');
$this->assertFalse($cache->contains('key1'));
$cache->save('key1', 'test');
$this->assertTrue($cache->contains('key1'));
$cache->setNamespace('ns1');
$this->assertTrue($cache->contains('key1'));
$cache->deleteAll();
$this->assertFalse($cache->contains('key1'));
$cache->setNamespace('ns2');
$this->assertTrue($cache->contains('key1'));
$cache->deleteAll();
$this->assertFalse($cache->contains('key1'));
}
/**
* @group DCOM-43
*/
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertArrayHasKey(Cache::STATS_HITS, $stats);
$this->assertArrayHasKey(Cache::STATS_MISSES, $stats);
$this->assertArrayHasKey(Cache::STATS_UPTIME, $stats);
$this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats);
$this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats);
}
public function testSaveReturnsTrueWithAndWithoutTTlSet()
{
$cache = $this->_getCacheDriver();
$cache->deleteAll();
$this->assertTrue($cache->save('without_ttl', 'without_ttl'));
$this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600));
}
/**
* Return whether multiple cache providers share the same storage.
*
* This is used for skipping certain tests for shared storage behavior.
*
* @return bool
*/
protected function isSharedStorage()
{
return true;
}
/**
* @return \Doctrine\Common\Cache\CacheProvider
*/
abstract protected function _getCacheDriver();
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\ApcCache;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\ChainCache;
class ChainCacheTest extends CacheTest
{
protected function _getCacheDriver()
{
return new ChainCache(array(new ArrayCache()));
}
public function testLifetime()
{
$this->markTestSkipped('The ChainCache test uses ArrayCache which does not implement TTL currently.');
}
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertInternalType('array', $stats);
}
public function testOnlyFetchFirstOne()
{
$cache1 = new ArrayCache();
$cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider');
$cache2->expects($this->never())->method('doFetch');
$chainCache = new ChainCache(array($cache1, $cache2));
$chainCache->save('id', 'bar');
$this->assertEquals('bar', $chainCache->fetch('id'));
}
public function testFetchPropagateToFastestCache()
{
$cache1 = new ArrayCache();
$cache2 = new ArrayCache();
$cache2->save('bar', 'value');
$chainCache = new ChainCache(array($cache1, $cache2));
$this->assertFalse($cache1->contains('bar'));
$result = $chainCache->fetch('bar');
$this->assertEquals('value', $result);
$this->assertTrue($cache2->contains('bar'));
}
public function testNamespaceIsPropagatedToAllProviders()
{
$cache1 = new ArrayCache();
$cache2 = new ArrayCache();
$chainCache = new ChainCache(array($cache1, $cache2));
$chainCache->setNamespace('bar');
$this->assertEquals('bar', $cache1->getNamespace());
$this->assertEquals('bar', $cache2->getNamespace());
}
public function testDeleteToAllProviders()
{
$cache1 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider');
$cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider');
$cache1->expects($this->once())->method('doDelete');
$cache2->expects($this->once())->method('doDelete');
$chainCache = new ChainCache(array($cache1, $cache2));
$chainCache->delete('bar');
}
public function testFlushToAllProviders()
{
$cache1 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider');
$cache2 = $this->getMockForAbstractClass('Doctrine\Common\Cache\CacheProvider');
$cache1->expects($this->once())->method('doFlush');
$cache2->expects($this->once())->method('doFlush');
$chainCache = new ChainCache(array($cache1, $cache2));
$chainCache->flushAll();
}
protected function isSharedStorage()
{
return false;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Couchbase;
use Doctrine\Common\Cache\CouchbaseCache;
/**
* @requires extension couchbase
*/
class CouchbaseCacheTest extends CacheTest
{
private $couchbase;
protected function setUp()
{
try {
$this->couchbase = new Couchbase('127.0.0.1', 'Administrator', 'password', 'default');
} catch(Exception $ex) {
$this->markTestSkipped('Could not instantiate the Couchbase cache because of: ' . $ex);
}
}
protected function _getCacheDriver()
{
$driver = new CouchbaseCache();
$driver->setCouchbase($this->couchbase);
return $driver;
}
}

View File

@@ -0,0 +1,256 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
/**
* @group DCOM-101
*/
class FileCacheTest extends \Doctrine\Tests\DoctrineTestCase
{
/**
* @var \Doctrine\Common\Cache\FileCache
*/
private $driver;
protected function setUp()
{
$this->driver = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array(), '', false
);
}
public function testFilenameShouldCreateThePathWithOneSubDirectory()
{
$cache = $this->driver;
$method = new \ReflectionMethod($cache, 'getFilename');
$key = 'item-key';
$expectedDir = array(
'84',
);
$expectedDir = implode(DIRECTORY_SEPARATOR, $expectedDir);
$method->setAccessible(true);
$path = $method->invoke($cache, $key);
$dirname = pathinfo($path, PATHINFO_DIRNAME);
$this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname);
}
public function testFileExtensionCorrectlyEscaped()
{
$driver1 = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array(__DIR__, '.*')
);
$driver2 = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array(__DIR__, '.php')
);
$doGetStats = new \ReflectionMethod($driver1, 'doGetStats');
$doGetStats->setAccessible(true);
$stats1 = $doGetStats->invoke($driver1);
$stats2 = $doGetStats->invoke($driver2);
$this->assertSame(0, $stats1[Cache::STATS_MEMORY_USAGE]);
$this->assertGreaterThan(0, $stats2[Cache::STATS_MEMORY_USAGE]);
}
/**
* @group DCOM-266
*/
public function testFileExtensionSlashCorrectlyEscaped()
{
$driver = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array(__DIR__ . '/../', DIRECTORY_SEPARATOR . basename(__FILE__))
);
$doGetStats = new \ReflectionMethod($driver, 'doGetStats');
$doGetStats->setAccessible(true);
$stats = $doGetStats->invoke($driver);
$this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_USAGE]);
}
public function testNonIntUmaskThrowsInvalidArgumentException()
{
$this->setExpectedException('InvalidArgumentException');
$this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array('', '', 'invalid')
);
}
public function testGetDirectoryReturnsRealpathDirectoryString()
{
$directory = __DIR__ . '/../';
$driver = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array($directory)
);
$doGetDirectory = new \ReflectionMethod($driver, 'getDirectory');
$actualDirectory = $doGetDirectory->invoke($driver);
$expectedDirectory = realpath($directory);
$this->assertEquals($expectedDirectory, $actualDirectory);
}
public function testGetExtensionReturnsExtensionString()
{
$directory = __DIR__ . '/../';
$extension = DIRECTORY_SEPARATOR . basename(__FILE__);
$driver = $this->getMock(
'Doctrine\Common\Cache\FileCache',
array('doFetch', 'doContains', 'doSave'),
array($directory, $extension)
);
$doGetExtension = new \ReflectionMethod($driver, 'getExtension');
$actualExtension = $doGetExtension->invoke($driver);
$this->assertEquals($extension, $actualExtension);
}
const WIN_MAX_PATH_LEN = 258;
public static function getBasePathForWindowsPathLengthTests($pathLength)
{
// Not using __DIR__ because it can get screwed up when xdebug debugger is attached.
$basePath = realpath(sys_get_temp_dir());
// Test whether the desired path length is odd or even.
$desiredPathLengthIsOdd = ($pathLength % 2) == 1;
// If the cache key is not too long, the filecache codepath will add
// a slash and bin2hex($key). The length of the added portion will be an odd number.
// len(desired) = len(base path) + len(slash . bin2hex($key))
// odd = even + odd
// even = odd + odd
$basePathLengthShouldBeOdd = !$desiredPathLengthIsOdd;
$basePathLengthIsOdd = (strlen($basePath) % 2) == 1;
// If the base path needs to be odd or even where it is not, we add an odd number of
// characters as a pad. In this case, we're adding '\aa' (or '/aa' depending on platform)
// This is all to make it so that the key we're testing would result in
// a path that is exactly the length we want to test IF the path length limit
// were not in place in FileCache.
if ($basePathLengthIsOdd != $basePathLengthShouldBeOdd) {
$basePath .= DIRECTORY_SEPARATOR . "aa";
}
return $basePath;
}
public static function getKeyAndPathFittingLength($length)
{
$basePath = self::getBasePathForWindowsPathLengthTests($length);
$baseDirLength = strlen($basePath);
$extensionLength = strlen('.doctrine.cache');
$directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR);
$keyLength = $length - ($baseDirLength + $extensionLength + $directoryLength); // - 1 because of slash
$key = str_repeat('a', floor($keyLength / 2));
$keyHash = hash('sha256', $key);
$keyPath = $basePath
. DIRECTORY_SEPARATOR
. substr($keyHash, 0, 2)
. DIRECTORY_SEPARATOR
. bin2hex($key)
. '.doctrine.cache';
$hashedKeyPath = $basePath
. DIRECTORY_SEPARATOR
. substr($keyHash, 0, 2)
. DIRECTORY_SEPARATOR
. '_' . $keyHash
. '.doctrine.cache';
return array($key, $keyPath, $hashedKeyPath);
}
public function getPathLengthsToTest()
{
// Windows officially supports 260 bytes including null terminator
// 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943)
// 260 characters is too large - null terminator is included in allowable length
return array(
array(257, false),
array(258, false),
array(259, true),
array(260, true)
);
}
/**
* @runInSeparateProcess
* @dataProvider getPathLengthsToTest
*
* @covers \Doctrine\Common\Cache\FileCache::getFilename
*/
public function testWindowsPathLengthLimitationsAreCorrectlyRespected($length, $pathShouldBeHashed)
{
if (! defined('PHP_WINDOWS_VERSION_BUILD')) {
define('PHP_WINDOWS_VERSION_BUILD', 'Yes, this is the "usual suspect", with the usual limitations');
}
$basePath = $this->getBasePathForWindowsPathLengthTests($length);
$fileCache = $this->getMockForAbstractClass(
'Doctrine\Common\Cache\FileCache',
array($basePath, '.doctrine.cache')
);
list($key, $keyPath, $hashedKeyPath) = $this->getKeyAndPathFittingLength($length);
$getFileName = new \ReflectionMethod($fileCache, 'getFilename');
$getFileName->setAccessible(true);
$this->assertEquals(
$length,
strlen($keyPath),
sprintf('Path expected to be %d characters long is %d characters long', $length, strlen($keyPath))
);
if ($pathShouldBeHashed) {
$keyPath = $hashedKeyPath;
}
if ($pathShouldBeHashed) {
$this->assertSame(
$hashedKeyPath,
$getFileName->invoke($fileCache, $key),
'Keys should be hashed correctly if they are over the limit.'
);
} else {
$this->assertSame(
$keyPath,
$getFileName->invoke($fileCache, $key),
'Keys below limit of the allowed length are used directly, unhashed'
);
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\FilesystemCache;
/**
* @group DCOM-101
*/
class FilesystemCacheTest extends BaseFileCacheTest
{
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats[Cache::STATS_HITS]);
$this->assertNull($stats[Cache::STATS_MISSES]);
$this->assertNull($stats[Cache::STATS_UPTIME]);
$this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]);
$this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]);
}
public function testCacheInSharedDirectoryIsPerExtension()
{
$cache1 = new FilesystemCache($this->directory, '.foo');
$cache2 = new FilesystemCache($this->directory, '.bar');
$this->assertTrue($cache1->save('key1', 11));
$this->assertTrue($cache1->save('key2', 12));
$this->assertTrue($cache2->save('key1', 21));
$this->assertTrue($cache2->save('key2', 22));
$this->assertSame(11, $cache1->fetch('key1'), 'Cache value must not be influenced by a different cache in the same directory but different extension');
$this->assertSame(12, $cache1->fetch('key2'));
$this->assertTrue($cache1->flushAll());
$this->assertFalse($cache1->fetch('key1'), 'flushAll() must delete all items with the current extension');
$this->assertFalse($cache1->fetch('key2'));
$this->assertSame(21, $cache2->fetch('key1'), 'flushAll() must not remove items with a different extension in a shared directory');
$this->assertSame(22, $cache2->fetch('key2'));
}
public function testFlushAllWithNoExtension()
{
$cache = new FilesystemCache($this->directory, '');
$this->assertTrue($cache->save('key1', 1));
$this->assertTrue($cache->save('key2', 2));
$this->assertTrue($cache->flushAll());
$this->assertFalse($cache->contains('key1'));
$this->assertFalse($cache->contains('key2'));
}
protected function _getCacheDriver()
{
return new FilesystemCache($this->directory);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\MemcacheCache;
use Memcache;
/**
* @requires extension memcache
*/
class MemcacheCacheTest extends CacheTest
{
private $memcache;
protected function setUp()
{
$this->memcache = new Memcache();
if (@$this->memcache->connect('localhost', 11211) === false) {
unset($this->memcache);
$this->markTestSkipped('Cannot connect to Memcache.');
}
}
protected function tearDown()
{
if ($this->memcache instanceof Memcache) {
$this->memcache->flush();
}
}
/**
* {@inheritdoc}
*
* Memcache does not support " " and null byte as key so we remove them from the tests.
*/
public function provideCacheIds()
{
$ids = parent::provideCacheIds();
unset($ids[21], $ids[22]);
return $ids;
}
public function testGetMemcacheReturnsInstanceOfMemcache()
{
$this->assertInstanceOf('Memcache', $this->_getCacheDriver()->getMemcache());
}
/**
* {@inheritDoc}
*/
protected function _getCacheDriver()
{
$driver = new MemcacheCache();
$driver->setMemcache($this->memcache);
return $driver;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\MemcachedCache;
use Memcached;
/**
* @requires extension memcached
*/
class MemcachedCacheTest extends CacheTest
{
private $memcached;
protected function setUp()
{
$this->memcached = new Memcached();
$this->memcached->setOption(Memcached::OPT_COMPRESSION, false);
$this->memcached->addServer('127.0.0.1', 11211);
if (@fsockopen('127.0.0.1', 11211) === false) {
unset($this->memcached);
$this->markTestSkipped('Cannot connect to Memcached.');
}
}
protected function tearDown()
{
if ($this->memcached instanceof Memcached) {
$this->memcached->flush();
}
}
/**
* {@inheritdoc}
*
* Memcached does not support " ", null byte and very long keys so we remove them from the tests.
*/
public function provideCacheIds()
{
$ids = parent::provideCacheIds();
unset($ids[21], $ids[22], $ids[24]);
return $ids;
}
public function testGetMemcachedReturnsInstanceOfMemcached()
{
$this->assertInstanceOf('Memcached', $this->_getCacheDriver()->getMemcached());
}
/**
* {@inheritDoc}
*/
protected function _getCacheDriver()
{
$driver = new MemcachedCache();
$driver->setMemcached($this->memcached);
return $driver;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\MongoDBCache;
use MongoClient;
use MongoCollection;
/**
* @requires extension mongo
*/
class MongoDBCacheTest extends CacheTest
{
/**
* @var MongoCollection
*/
private $collection;
protected function setUp()
{
if ( ! version_compare(phpversion('mongo'), '1.3.0', '>=')) {
$this->markTestSkipped('Mongo >= 1.3.0 is required.');
}
$mongo = new MongoClient();
$this->collection = $mongo->selectCollection('doctrine_common_cache', 'test');
}
protected function tearDown()
{
if ($this->collection instanceof MongoCollection) {
$this->collection->drop();
}
}
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats[Cache::STATS_HITS]);
$this->assertNull($stats[Cache::STATS_MISSES]);
$this->assertGreaterThan(0, $stats[Cache::STATS_UPTIME]);
$this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]);
$this->assertNull($stats[Cache::STATS_MEMORY_AVAILABLE]);
}
/**
* @group 108
*/
public function testMongoCursorExceptionsDoNotBubbleUp()
{
/* @var $collection \MongoCollection|\PHPUnit_Framework_MockObject_MockObject */
$collection = $this->getMock('MongoCollection');
$collection->expects(self::once())->method('update')->willThrowException(new \MongoCursorException());
$cache = new MongoDBCache($collection);
self::assertFalse($cache->save('foo', 'bar'));
}
protected function _getCacheDriver()
{
return new MongoDBCache($this->collection);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\PhpFileCache;
/**
* @group DCOM-101
*/
class PhpFileCacheTest extends BaseFileCacheTest
{
public function provideDataToCache()
{
$data = parent::provideDataToCache();
unset($data['object'], $data['object_recursive']); // PhpFileCache only allows objects that implement __set_state() and fully support var_export()
unset($data['float_zero']); // var_export exports float(0) as int(0)
return $data;
}
public function testImplementsSetState()
{
$cache = $this->_getCacheDriver();
// Test save
$cache->save('test_set_state', new SetStateClass(array(1,2,3)));
//Test __set_state call
$this->assertCount(0, SetStateClass::$values);
// Test fetch
$value = $cache->fetch('test_set_state');
$this->assertInstanceOf('Doctrine\Tests\Common\Cache\SetStateClass', $value);
$this->assertEquals(array(1,2,3), $value->getValue());
//Test __set_state call
$this->assertCount(1, SetStateClass::$values);
// Test contains
$this->assertTrue($cache->contains('test_set_state'));
}
public function testNotImplementsSetState()
{
$cache = $this->_getCacheDriver();
$this->setExpectedException('InvalidArgumentException');
$cache->save('test_not_set_state', new NotSetStateClass(array(1,2,3)));
}
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats[Cache::STATS_HITS]);
$this->assertNull($stats[Cache::STATS_MISSES]);
$this->assertNull($stats[Cache::STATS_UPTIME]);
$this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]);
$this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]);
}
protected function _getCacheDriver()
{
return new PhpFileCache($this->directory);
}
}
class NotSetStateClass
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
class SetStateClass extends NotSetStateClass
{
public static $values = array();
public static function __set_state($data)
{
self::$values = $data;
return new self($data['value']);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\PredisCache;
use Predis\Client;
use Predis\Connection\ConnectionException;
class PredisCacheTest extends CacheTest
{
private $client;
protected function setUp()
{
if (!class_exists('Predis\Client')) {
$this->markTestSkipped('Predis\Client is missing. Make sure to "composer install" to have all dev dependencies.');
}
$this->client = new Client();
try {
$this->client->connect();
} catch (ConnectionException $e) {
$this->markTestSkipped('Cannot connect to Redis because of: ' . $e);
}
}
public function testHitMissesStatsAreProvided()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNotNull($stats[Cache::STATS_HITS]);
$this->assertNotNull($stats[Cache::STATS_MISSES]);
}
/**
* @return PredisCache
*/
protected function _getCacheDriver()
{
return new PredisCache($this->client);
}
/**
* {@inheritDoc}
*
* @dataProvider provideDataToCache
*/
public function testSetContainsFetchDelete($value)
{
if (array() === $value) {
$this->markTestIncomplete(
'Predis currently doesn\'t support saving empty array values. '
. 'See https://github.com/nrk/predis/issues/241'
);
}
parent::testSetContainsFetchDelete($value);
}
/**
* {@inheritDoc}
*
* @dataProvider provideDataToCache
*/
public function testUpdateExistingEntry($value)
{
if (array() === $value) {
$this->markTestIncomplete(
'Predis currently doesn\'t support saving empty array values. '
. 'See https://github.com/nrk/predis/issues/241'
);
}
parent::testUpdateExistingEntry($value);
}
public function testAllowsGenericPredisClient()
{
/* @var $predisClient \Predis\ClientInterface */
$predisClient = $this->getMock('Predis\\ClientInterface');
$this->assertInstanceOf('Doctrine\\Common\\Cache\\PredisCache', new PredisCache($predisClient));
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\RedisCache;
use Doctrine\Common\Cache\Cache;
/**
* @requires extension redis
*/
class RedisCacheTest extends CacheTest
{
private $_redis;
protected function setUp()
{
$this->_redis = new \Redis();
$ok = @$this->_redis->connect('127.0.0.1');
if (!$ok) {
$this->markTestSkipped('Cannot connect to Redis.');
}
}
public function testHitMissesStatsAreProvided()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNotNull($stats[Cache::STATS_HITS]);
$this->assertNotNull($stats[Cache::STATS_MISSES]);
}
public function testGetRedisReturnsInstanceOfRedis()
{
$this->assertInstanceOf('Redis', $this->_getCacheDriver()->getRedis());
}
/**
* {@inheritDoc}
*/
protected function _getCacheDriver()
{
$driver = new RedisCache();
$driver->setRedis($this->_redis);
return $driver;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Riak\Bucket;
use Riak\Connection;
use Riak\Exception;
use Doctrine\Common\Cache\RiakCache;
/**
* RiakCache test
*
* @group Riak
* @requires extension riak
*/
class RiakCacheTest extends CacheTest
{
/**
* @var \Riak\Connection
*/
private $connection;
/**
* @var \Riak\Bucket
*/
private $bucket;
protected function setUp()
{
try {
$this->connection = new Connection('127.0.0.1', 8087);
$this->bucket = new Bucket($this->connection, 'test');
} catch (Exception\RiakException $e) {
$this->markTestSkipped('Cannot connect to Riak.');
}
}
/**
* {@inheritdoc}
*/
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats);
}
/**
* Retrieve RiakCache instance.
*
* @return \Doctrine\Common\Cache\RiakCache
*/
protected function _getCacheDriver()
{
return new RiakCache($this->bucket);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\SQLite3Cache;
use SQLite3;
/**
* @requires extension sqlite3
*/
class SQLite3Test extends CacheTest
{
private $file;
private $sqlite;
protected function setUp()
{
$this->file = tempnam(null, 'doctrine-cache-test-');
unlink($this->file);
$this->sqlite = new SQLite3($this->file);
}
protected function tearDown()
{
$this->sqlite = null; // DB must be closed before
unlink($this->file);
}
public function testGetStats()
{
$this->assertNull($this->_getCacheDriver()->getStats());
}
/**
* {@inheritDoc}
*/
protected function _getCacheDriver()
{
return new SQLite3Cache($this->sqlite, 'test_table');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\VoidCache;
/**
* @covers \Doctrine\Common\Cache\VoidCache
*/
class VoidCacheTest extends \PHPUnit_Framework_TestCase
{
public function testShouldAlwaysReturnFalseOnContains()
{
$cache = new VoidCache();
$this->assertFalse($cache->contains('foo'));
$this->assertFalse($cache->contains('bar'));
}
public function testShouldAlwaysReturnFalseOnFetch()
{
$cache = new VoidCache();
$this->assertFalse($cache->fetch('foo'));
$this->assertFalse($cache->fetch('bar'));
}
public function testShouldAlwaysReturnTrueOnSaveButNotStoreAnything()
{
$cache = new VoidCache();
$this->assertTrue($cache->save('foo', 'fooVal'));
$this->assertFalse($cache->contains('foo'));
$this->assertFalse($cache->fetch('foo'));
}
public function testShouldAlwaysReturnTrueOnDelete()
{
$cache = new VoidCache();
$this->assertTrue($cache->delete('foo'));
}
public function testShouldAlwaysReturnNullOnGetStatus()
{
$cache = new VoidCache();
$this->assertNull($cache->getStats());
}
public function testShouldAlwaysReturnTrueOnFlush()
{
$cache = new VoidCache();
$this->assertTrue($cache->flushAll());
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\WincacheCache;
/**
* @requires extension wincache
*/
class WincacheCacheTest extends CacheTest
{
protected function _getCacheDriver()
{
return new WincacheCache();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\XcacheCache;
/**
* @requires extension xcache
*/
class XcacheCacheTest extends CacheTest
{
protected function _getCacheDriver()
{
return new XcacheCache();
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Doctrine\Tests\Common\Cache;
use Doctrine\Common\Cache\ZendDataCache;
/**
* @requires function zend_shm_cache_fetch
*/
class ZendDataCacheTest extends CacheTest
{
protected function setUp()
{
if ('apache2handler' !== php_sapi_name()) {
$this->markTestSkipped('Zend Data Cache only works in apache2handler SAPI.');
}
}
public function testGetStats()
{
$cache = $this->_getCacheDriver();
$stats = $cache->getStats();
$this->assertNull($stats);
}
protected function _getCacheDriver()
{
return new ZendDataCache();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Doctrine\Tests;
/**
* Base testcase class for all Doctrine testcases.
*/
abstract class DoctrineTestCase extends \PHPUnit_Framework_TestCase
{
}

View File

@@ -0,0 +1,7 @@
extension="mongo.so"
extension="memcache.so"
extension="memcached.so"
extension="redis.so"
apc.enabled=1
apc.enable_cli=1

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="../../vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<logging>
<log type="coverage-clover" target="../../build/logs/clover.xml"/>
</logging>
<testsuites>
<testsuite name="Doctrine Cache Test Suite">
<directory>../Doctrine/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>../../lib/Doctrine/</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
</exclude>
</groups>
</phpunit>

View File

@@ -0,0 +1 @@
vendor/

View File

@@ -0,0 +1,21 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
- hhvm-nightly
matrix:
fast_finish: true
allow_failures:
- php: 7.0
before_script:
- composer --prefer-source install
script:
- phpunit

View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,25 @@
# Doctrine Collections
[![Build Status](https://travis-ci.org/doctrine/collections.svg?branch=master)](https://travis-ci.org/doctrine/collections)
Collections Abstraction library
## Changelog
### v1.3.0
* [Explicit casting of first and max results in criteria API](https://github.com/doctrine/collections/pull/26)
* [Keep keys when using `ArrayCollection#matching()` with sorting](https://github.com/doctrine/collections/pull/49)
* [Made `AbstractLazyCollection#$initialized` protected for extensibility](https://github.com/doctrine/collections/pull/52)
### v1.2.0
* Add a new ``AbstractLazyCollection``
### v1.1.0
* Deprecated ``Comparison::IS``, because it's only there for SQL semantics.
These are fixed in the ORM instead.
* Add ``Comparison::CONTAINS`` to perform partial string matches:
$criteria->andWhere($criteria->expr()->contains('property', 'Foo'));

View File

@@ -0,0 +1,29 @@
{
"name": "doctrine/collections",
"type": "library",
"description": "Collections Abstraction library",
"keywords": ["collections", "array", "iterator"],
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-0": { "Doctrine\\Common\\Collections\\": "lib/" }
},
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
}
}

View File

@@ -0,0 +1,343 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use Closure;
/**
* Lazy collection that is backed by a concrete collection
*
* @author Michaël Gallego <mic.gallego@gmail.com>
* @since 1.2
*/
abstract class AbstractLazyCollection implements Collection
{
/**
* The backed collection to use
*
* @var Collection
*/
protected $collection;
/**
* @var boolean
*/
protected $initialized = false;
/**
* {@inheritDoc}
*/
public function count()
{
$this->initialize();
return $this->collection->count();
}
/**
* {@inheritDoc}
*/
public function add($element)
{
$this->initialize();
return $this->collection->add($element);
}
/**
* {@inheritDoc}
*/
public function clear()
{
$this->initialize();
$this->collection->clear();
}
/**
* {@inheritDoc}
*/
public function contains($element)
{
$this->initialize();
return $this->collection->contains($element);
}
/**
* {@inheritDoc}
*/
public function isEmpty()
{
$this->initialize();
return $this->collection->isEmpty();
}
/**
* {@inheritDoc}
*/
public function remove($key)
{
$this->initialize();
return $this->collection->remove($key);
}
/**
* {@inheritDoc}
*/
public function removeElement($element)
{
$this->initialize();
return $this->collection->removeElement($element);
}
/**
* {@inheritDoc}
*/
public function containsKey($key)
{
$this->initialize();
return $this->collection->containsKey($key);
}
/**
* {@inheritDoc}
*/
public function get($key)
{
$this->initialize();
return $this->collection->get($key);
}
/**
* {@inheritDoc}
*/
public function getKeys()
{
$this->initialize();
return $this->collection->getKeys();
}
/**
* {@inheritDoc}
*/
public function getValues()
{
$this->initialize();
return $this->collection->getValues();
}
/**
* {@inheritDoc}
*/
public function set($key, $value)
{
$this->initialize();
$this->collection->set($key, $value);
}
/**
* {@inheritDoc}
*/
public function toArray()
{
$this->initialize();
return $this->collection->toArray();
}
/**
* {@inheritDoc}
*/
public function first()
{
$this->initialize();
return $this->collection->first();
}
/**
* {@inheritDoc}
*/
public function last()
{
$this->initialize();
return $this->collection->last();
}
/**
* {@inheritDoc}
*/
public function key()
{
$this->initialize();
return $this->collection->key();
}
/**
* {@inheritDoc}
*/
public function current()
{
$this->initialize();
return $this->collection->current();
}
/**
* {@inheritDoc}
*/
public function next()
{
$this->initialize();
return $this->collection->next();
}
/**
* {@inheritDoc}
*/
public function exists(Closure $p)
{
$this->initialize();
return $this->collection->exists($p);
}
/**
* {@inheritDoc}
*/
public function filter(Closure $p)
{
$this->initialize();
return $this->collection->filter($p);
}
/**
* {@inheritDoc}
*/
public function forAll(Closure $p)
{
$this->initialize();
return $this->collection->forAll($p);
}
/**
* {@inheritDoc}
*/
public function map(Closure $func)
{
$this->initialize();
return $this->collection->map($func);
}
/**
* {@inheritDoc}
*/
public function partition(Closure $p)
{
$this->initialize();
return $this->collection->partition($p);
}
/**
* {@inheritDoc}
*/
public function indexOf($element)
{
$this->initialize();
return $this->collection->indexOf($element);
}
/**
* {@inheritDoc}
*/
public function slice($offset, $length = null)
{
$this->initialize();
return $this->collection->slice($offset, $length);
}
/**
* {@inheritDoc}
*/
public function getIterator()
{
$this->initialize();
return $this->collection->getIterator();
}
/**
* {@inheritDoc}
*/
public function offsetExists($offset)
{
$this->initialize();
return $this->collection->offsetExists($offset);
}
/**
* {@inheritDoc}
*/
public function offsetGet($offset)
{
$this->initialize();
return $this->collection->offsetGet($offset);
}
/**
* {@inheritDoc}
*/
public function offsetSet($offset, $value)
{
$this->initialize();
$this->collection->offsetSet($offset, $value);
}
/**
* {@inheritDoc}
*/
public function offsetUnset($offset)
{
$this->initialize();
$this->collection->offsetUnset($offset);
}
/**
* Is the lazy collection already initialized?
*
* @return bool
*/
public function isInitialized()
{
return $this->initialized;
}
/**
* Initialize the collection
*
* @return void
*/
protected function initialize()
{
if ( ! $this->initialized) {
$this->doInitialize();
$this->initialized = true;
}
}
/**
* Do the initialization logic
*
* @return void
*/
abstract protected function doInitialize();
}

View File

@@ -0,0 +1,387 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use ArrayIterator;
use Closure;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
/**
* An ArrayCollection is a Collection implementation that wraps a regular PHP array.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class ArrayCollection implements Collection, Selectable
{
/**
* An array containing the entries of this collection.
*
* @var array
*/
private $elements;
/**
* Initializes a new ArrayCollection.
*
* @param array $elements
*/
public function __construct(array $elements = array())
{
$this->elements = $elements;
}
/**
* {@inheritDoc}
*/
public function toArray()
{
return $this->elements;
}
/**
* {@inheritDoc}
*/
public function first()
{
return reset($this->elements);
}
/**
* {@inheritDoc}
*/
public function last()
{
return end($this->elements);
}
/**
* {@inheritDoc}
*/
public function key()
{
return key($this->elements);
}
/**
* {@inheritDoc}
*/
public function next()
{
return next($this->elements);
}
/**
* {@inheritDoc}
*/
public function current()
{
return current($this->elements);
}
/**
* {@inheritDoc}
*/
public function remove($key)
{
if ( ! isset($this->elements[$key]) && ! array_key_exists($key, $this->elements)) {
return null;
}
$removed = $this->elements[$key];
unset($this->elements[$key]);
return $removed;
}
/**
* {@inheritDoc}
*/
public function removeElement($element)
{
$key = array_search($element, $this->elements, true);
if ($key === false) {
return false;
}
unset($this->elements[$key]);
return true;
}
/**
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*/
public function offsetExists($offset)
{
return $this->containsKey($offset);
}
/**
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*/
public function offsetSet($offset, $value)
{
if ( ! isset($offset)) {
return $this->add($value);
}
$this->set($offset, $value);
}
/**
* Required by interface ArrayAccess.
*
* {@inheritDoc}
*/
public function offsetUnset($offset)
{
return $this->remove($offset);
}
/**
* {@inheritDoc}
*/
public function containsKey($key)
{
return isset($this->elements[$key]) || array_key_exists($key, $this->elements);
}
/**
* {@inheritDoc}
*/
public function contains($element)
{
return in_array($element, $this->elements, true);
}
/**
* {@inheritDoc}
*/
public function exists(Closure $p)
{
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
public function indexOf($element)
{
return array_search($element, $this->elements, true);
}
/**
* {@inheritDoc}
*/
public function get($key)
{
return isset($this->elements[$key]) ? $this->elements[$key] : null;
}
/**
* {@inheritDoc}
*/
public function getKeys()
{
return array_keys($this->elements);
}
/**
* {@inheritDoc}
*/
public function getValues()
{
return array_values($this->elements);
}
/**
* {@inheritDoc}
*/
public function count()
{
return count($this->elements);
}
/**
* {@inheritDoc}
*/
public function set($key, $value)
{
$this->elements[$key] = $value;
}
/**
* {@inheritDoc}
*/
public function add($value)
{
$this->elements[] = $value;
return true;
}
/**
* {@inheritDoc}
*/
public function isEmpty()
{
return empty($this->elements);
}
/**
* Required by interface IteratorAggregate.
*
* {@inheritDoc}
*/
public function getIterator()
{
return new ArrayIterator($this->elements);
}
/**
* {@inheritDoc}
*/
public function map(Closure $func)
{
return new static(array_map($func, $this->elements));
}
/**
* {@inheritDoc}
*/
public function filter(Closure $p)
{
return new static(array_filter($this->elements, $p));
}
/**
* {@inheritDoc}
*/
public function forAll(Closure $p)
{
foreach ($this->elements as $key => $element) {
if ( ! $p($key, $element)) {
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
public function partition(Closure $p)
{
$matches = $noMatches = array();
foreach ($this->elements as $key => $element) {
if ($p($key, $element)) {
$matches[$key] = $element;
} else {
$noMatches[$key] = $element;
}
}
return array(new static($matches), new static($noMatches));
}
/**
* Returns a string representation of this object.
*
* @return string
*/
public function __toString()
{
return __CLASS__ . '@' . spl_object_hash($this);
}
/**
* {@inheritDoc}
*/
public function clear()
{
$this->elements = array();
}
/**
* {@inheritDoc}
*/
public function slice($offset, $length = null)
{
return array_slice($this->elements, $offset, $length, true);
}
/**
* {@inheritDoc}
*/
public function matching(Criteria $criteria)
{
$expr = $criteria->getWhereExpression();
$filtered = $this->elements;
if ($expr) {
$visitor = new ClosureExpressionVisitor();
$filter = $visitor->dispatch($expr);
$filtered = array_filter($filtered, $filter);
}
if ($orderings = $criteria->getOrderings()) {
foreach (array_reverse($orderings) as $field => $ordering) {
$next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1);
}
uasort($filtered, $next);
}
$offset = $criteria->getFirstResult();
$length = $criteria->getMaxResults();
if ($offset || $length) {
$filtered = array_slice($filtered, (int)$offset, $length);
}
return new static($filtered);
}
}

View File

@@ -0,0 +1,263 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use ArrayAccess;
use Closure;
use Countable;
use IteratorAggregate;
/**
* The missing (SPL) Collection/Array/OrderedMap interface.
*
* A Collection resembles the nature of a regular PHP array. That is,
* it is essentially an <b>ordered map</b> that can also be used
* like a list.
*
* A Collection has an internal iterator just like a PHP array. In addition,
* a Collection can be iterated with external iterators, which is preferable.
* To use an external iterator simply use the foreach language construct to
* iterate over the collection (which calls {@link getIterator()} internally) or
* explicitly retrieve an iterator though {@link getIterator()} which can then be
* used to iterate over the collection.
* You can not rely on the internal iterator of the collection being at a certain
* position unless you explicitly positioned it before. Prefer iteration with
* external iterators.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
interface Collection extends Countable, IteratorAggregate, ArrayAccess
{
/**
* Adds an element at the end of the collection.
*
* @param mixed $element The element to add.
*
* @return boolean Always TRUE.
*/
public function add($element);
/**
* Clears the collection, removing all elements.
*
* @return void
*/
public function clear();
/**
* Checks whether an element is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @param mixed $element The element to search for.
*
* @return boolean TRUE if the collection contains the element, FALSE otherwise.
*/
public function contains($element);
/**
* Checks whether the collection is empty (contains no elements).
*
* @return boolean TRUE if the collection is empty, FALSE otherwise.
*/
public function isEmpty();
/**
* Removes the element at the specified index from the collection.
*
* @param string|integer $key The kex/index of the element to remove.
*
* @return mixed The removed element or NULL, if the collection did not contain the element.
*/
public function remove($key);
/**
* Removes the specified element from the collection, if it is found.
*
* @param mixed $element The element to remove.
*
* @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
*/
public function removeElement($element);
/**
* Checks whether the collection contains an element with the specified key/index.
*
* @param string|integer $key The key/index to check for.
*
* @return boolean TRUE if the collection contains an element with the specified key/index,
* FALSE otherwise.
*/
public function containsKey($key);
/**
* Gets the element at the specified key/index.
*
* @param string|integer $key The key/index of the element to retrieve.
*
* @return mixed
*/
public function get($key);
/**
* Gets all keys/indices of the collection.
*
* @return array The keys/indices of the collection, in the order of the corresponding
* elements in the collection.
*/
public function getKeys();
/**
* Gets all values of the collection.
*
* @return array The values of all elements in the collection, in the order they
* appear in the collection.
*/
public function getValues();
/**
* Sets an element in the collection at the specified key/index.
*
* @param string|integer $key The key/index of the element to set.
* @param mixed $value The element to set.
*
* @return void
*/
public function set($key, $value);
/**
* Gets a native PHP array representation of the collection.
*
* @return array
*/
public function toArray();
/**
* Sets the internal iterator to the first element in the collection and returns this element.
*
* @return mixed
*/
public function first();
/**
* Sets the internal iterator to the last element in the collection and returns this element.
*
* @return mixed
*/
public function last();
/**
* Gets the key/index of the element at the current iterator position.
*
* @return int|string
*/
public function key();
/**
* Gets the element of the collection at the current iterator position.
*
* @return mixed
*/
public function current();
/**
* Moves the internal iterator position to the next element and returns this element.
*
* @return mixed
*/
public function next();
/**
* Tests for the existence of an element that satisfies the given predicate.
*
* @param Closure $p The predicate.
*
* @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise.
*/
public function exists(Closure $p);
/**
* Returns all the elements of this collection that satisfy the predicate p.
* The order of the elements is preserved.
*
* @param Closure $p The predicate used for filtering.
*
* @return Collection A collection with the results of the filter operation.
*/
public function filter(Closure $p);
/**
* Tests whether the given predicate p holds for all elements of this collection.
*
* @param Closure $p The predicate.
*
* @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.
*/
public function forAll(Closure $p);
/**
* Applies the given function to each element in the collection and returns
* a new collection with the elements returned by the function.
*
* @param Closure $func
*
* @return Collection
*/
public function map(Closure $func);
/**
* Partitions this collection in two collections according to a predicate.
* Keys are preserved in the resulting collections.
*
* @param Closure $p The predicate on which to partition.
*
* @return array An array with two elements. The first element contains the collection
* of elements where the predicate returned TRUE, the second element
* contains the collection of elements where the predicate returned FALSE.
*/
public function partition(Closure $p);
/**
* Gets the index/key of a given element. The comparison of two elements is strict,
* that means not only the value but also the type must match.
* For objects this means reference equality.
*
* @param mixed $element The element to search for.
*
* @return int|string|bool The key/index of the element or FALSE if the element was not found.
*/
public function indexOf($element);
/**
* Extracts a slice of $length elements starting at position $offset from the Collection.
*
* If $length is null it returns all elements from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the elements contained in the collection slice is called on.
*
* @param int $offset The offset to start from.
* @param int|null $length The maximum number of elements to return, or null for no limit.
*
* @return array
*/
public function slice($offset, $length = null);
}

View File

@@ -0,0 +1,259 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\Expression;
use Doctrine\Common\Collections\Expr\CompositeExpression;
/**
* Criteria for filtering Selectable collections.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class Criteria
{
/**
* @var string
*/
const ASC = 'ASC';
/**
* @var string
*/
const DESC = 'DESC';
/**
* @var \Doctrine\Common\Collections\ExpressionBuilder|null
*/
private static $expressionBuilder;
/**
* @var \Doctrine\Common\Collections\Expr\Expression|null
*/
private $expression;
/**
* @var string[]
*/
private $orderings = array();
/**
* @var int|null
*/
private $firstResult;
/**
* @var int|null
*/
private $maxResults;
/**
* Creates an instance of the class.
*
* @return Criteria
*/
public static function create()
{
return new static();
}
/**
* Returns the expression builder.
*
* @return \Doctrine\Common\Collections\ExpressionBuilder
*/
public static function expr()
{
if (self::$expressionBuilder === null) {
self::$expressionBuilder = new ExpressionBuilder();
}
return self::$expressionBuilder;
}
/**
* Construct a new Criteria.
*
* @param Expression $expression
* @param string[]|null $orderings
* @param int|null $firstResult
* @param int|null $maxResults
*/
public function __construct(Expression $expression = null, array $orderings = null, $firstResult = null, $maxResults = null)
{
$this->expression = $expression;
$this->setFirstResult($firstResult);
$this->setMaxResults($maxResults);
if (null !== $orderings) {
$this->orderBy($orderings);
}
}
/**
* Sets the where expression to evaluate when this Criteria is searched for.
*
* @param Expression $expression
*
* @return Criteria
*/
public function where(Expression $expression)
{
$this->expression = $expression;
return $this;
}
/**
* Appends the where expression to evaluate when this Criteria is searched for
* using an AND with previous expression.
*
* @param Expression $expression
*
* @return Criteria
*/
public function andWhere(Expression $expression)
{
if ($this->expression === null) {
return $this->where($expression);
}
$this->expression = new CompositeExpression(CompositeExpression::TYPE_AND, array(
$this->expression, $expression
));
return $this;
}
/**
* Appends the where expression to evaluate when this Criteria is searched for
* using an OR with previous expression.
*
* @param Expression $expression
*
* @return Criteria
*/
public function orWhere(Expression $expression)
{
if ($this->expression === null) {
return $this->where($expression);
}
$this->expression = new CompositeExpression(CompositeExpression::TYPE_OR, array(
$this->expression, $expression
));
return $this;
}
/**
* Gets the expression attached to this Criteria.
*
* @return Expression|null
*/
public function getWhereExpression()
{
return $this->expression;
}
/**
* Gets the current orderings of this Criteria.
*
* @return string[]
*/
public function getOrderings()
{
return $this->orderings;
}
/**
* Sets the ordering of the result of this Criteria.
*
* Keys are field and values are the order, being either ASC or DESC.
*
* @see Criteria::ASC
* @see Criteria::DESC
*
* @param string[] $orderings
*
* @return Criteria
*/
public function orderBy(array $orderings)
{
$this->orderings = array_map(
function ($ordering) {
return strtoupper($ordering) === Criteria::ASC ? Criteria::ASC : Criteria::DESC;
},
$orderings
);
return $this;
}
/**
* Gets the current first result option of this Criteria.
*
* @return int|null
*/
public function getFirstResult()
{
return $this->firstResult;
}
/**
* Set the number of first result that this Criteria should return.
*
* @param int|null $firstResult The value to set.
*
* @return Criteria
*/
public function setFirstResult($firstResult)
{
$this->firstResult = null === $firstResult ? null : (int) $firstResult;
return $this;
}
/**
* Gets maxResults.
*
* @return int|null
*/
public function getMaxResults()
{
return $this->maxResults;
}
/**
* Sets maxResults.
*
* @param int|null $maxResults The value to set.
*
* @return Criteria
*/
public function setMaxResults($maxResults)
{
$this->maxResults = null === $maxResults ? null : (int) $maxResults;
return $this;
}
}

View File

@@ -0,0 +1,227 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Walks an expression graph and turns it into a PHP closure.
*
* This closure can be used with {@Collection#filter()} and is used internally
* by {@ArrayCollection#select()}.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class ClosureExpressionVisitor extends ExpressionVisitor
{
/**
* Accesses the field of a given object. This field has to be public
* directly or indirectly (through an accessor get*, is*, or a magic
* method, __get, __call).
*
* @param object $object
* @param string $field
*
* @return mixed
*/
public static function getObjectFieldValue($object, $field)
{
if (is_array($object)) {
return $object[$field];
}
$accessors = array('get', 'is');
foreach ($accessors as $accessor) {
$accessor .= $field;
if ( ! method_exists($object, $accessor)) {
continue;
}
return $object->$accessor();
}
// __call should be triggered for get.
$accessor = $accessors[0] . $field;
if (method_exists($object, '__call')) {
return $object->$accessor();
}
if ($object instanceof \ArrayAccess) {
return $object[$field];
}
return $object->$field;
}
/**
* Helper for sorting arrays of objects based on multiple fields + orientations.
*
* @param string $name
* @param int $orientation
* @param \Closure $next
*
* @return \Closure
*/
public static function sortByField($name, $orientation = 1, \Closure $next = null)
{
if ( ! $next) {
$next = function() {
return 0;
};
}
return function ($a, $b) use ($name, $next, $orientation) {
$aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name);
$bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name);
if ($aValue === $bValue) {
return $next($a, $b);
}
return (($aValue > $bValue) ? 1 : -1) * $orientation;
};
}
/**
* {@inheritDoc}
*/
public function walkComparison(Comparison $comparison)
{
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
switch ($comparison->getOperator()) {
case Comparison::EQ:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value;
};
case Comparison::NEQ:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value;
};
case Comparison::LT:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value;
};
case Comparison::LTE:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value;
};
case Comparison::GT:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value;
};
case Comparison::GTE:
return function ($object) use ($field, $value) {
return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value;
};
case Comparison::IN:
return function ($object) use ($field, $value) {
return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
};
case Comparison::NIN:
return function ($object) use ($field, $value) {
return ! in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
};
case Comparison::CONTAINS:
return function ($object) use ($field, $value) {
return false !== strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
};
default:
throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
}
}
/**
* {@inheritDoc}
*/
public function walkValue(Value $value)
{
return $value->getValue();
}
/**
* {@inheritDoc}
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
$expressionList = array();
foreach ($expr->getExpressionList() as $child) {
$expressionList[] = $this->dispatch($child);
}
switch($expr->getType()) {
case CompositeExpression::TYPE_AND:
return $this->andExpressions($expressionList);
case CompositeExpression::TYPE_OR:
return $this->orExpressions($expressionList);
default:
throw new \RuntimeException("Unknown composite " . $expr->getType());
}
}
/**
* @param array $expressions
*
* @return callable
*/
private function andExpressions($expressions)
{
return function ($object) use ($expressions) {
foreach ($expressions as $expression) {
if ( ! $expression($object)) {
return false;
}
}
return true;
};
}
/**
* @param array $expressions
*
* @return callable
*/
private function orExpressions($expressions)
{
return function ($object) use ($expressions) {
foreach ($expressions as $expression) {
if ($expression($object)) {
return true;
}
}
return false;
};
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Comparison of a field with a value by the given operator.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class Comparison implements Expression
{
const EQ = '=';
const NEQ = '<>';
const LT = '<';
const LTE = '<=';
const GT = '>';
const GTE = '>=';
const IS = '='; // no difference with EQ
const IN = 'IN';
const NIN = 'NIN';
const CONTAINS = 'CONTAINS';
/**
* @var string
*/
private $field;
/**
* @var string
*/
private $op;
/**
* @var Value
*/
private $value;
/**
* @param string $field
* @param string $operator
* @param mixed $value
*/
public function __construct($field, $operator, $value)
{
if ( ! ($value instanceof Value)) {
$value = new Value($value);
}
$this->field = $field;
$this->op = $operator;
$this->value = $value;
}
/**
* @return string
*/
public function getField()
{
return $this->field;
}
/**
* @return Value
*/
public function getValue()
{
return $this->value;
}
/**
* @return string
*/
public function getOperator()
{
return $this->op;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkComparison($this);
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Expression of Expressions combined by AND or OR operation.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class CompositeExpression implements Expression
{
const TYPE_AND = 'AND';
const TYPE_OR = 'OR';
/**
* @var string
*/
private $type;
/**
* @var Expression[]
*/
private $expressions = array();
/**
* @param string $type
* @param array $expressions
*
* @throws \RuntimeException
*/
public function __construct($type, array $expressions)
{
$this->type = $type;
foreach ($expressions as $expr) {
if ($expr instanceof Value) {
throw new \RuntimeException("Values are not supported expressions as children of and/or expressions.");
}
if ( ! ($expr instanceof Expression)) {
throw new \RuntimeException("No expression given to CompositeExpression.");
}
$this->expressions[] = $expr;
}
}
/**
* Returns the list of expressions nested in this composite.
*
* @return Expression[]
*/
public function getExpressionList()
{
return $this->expressions;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkCompositeExpression($this);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* Expression for the {@link Selectable} interface.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface Expression
{
/**
* @param ExpressionVisitor $visitor
*
* @return mixed
*/
public function visit(ExpressionVisitor $visitor);
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
/**
* An Expression visitor walks a graph of expressions and turns them into a
* query for the underlying implementation.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
abstract class ExpressionVisitor
{
/**
* Converts a comparison expression into the target query language output.
*
* @param Comparison $comparison
*
* @return mixed
*/
abstract public function walkComparison(Comparison $comparison);
/**
* Converts a value expression into the target query language part.
*
* @param Value $value
*
* @return mixed
*/
abstract public function walkValue(Value $value);
/**
* Converts a composite expression into the target query language output.
*
* @param CompositeExpression $expr
*
* @return mixed
*/
abstract public function walkCompositeExpression(CompositeExpression $expr);
/**
* Dispatches walking an expression to the appropriate handler.
*
* @param Expression $expr
*
* @return mixed
*
* @throws \RuntimeException
*/
public function dispatch(Expression $expr)
{
switch (true) {
case ($expr instanceof Comparison):
return $this->walkComparison($expr);
case ($expr instanceof Value):
return $this->walkValue($expr);
case ($expr instanceof CompositeExpression):
return $this->walkCompositeExpression($expr);
default:
throw new \RuntimeException("Unknown Expression " . get_class($expr));
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections\Expr;
class Value implements Expression
{
/**
* @var mixed
*/
private $value;
/**
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* {@inheritDoc}
*/
public function visit(ExpressionVisitor $visitor)
{
return $visitor->walkValue($this);
}
}

View File

@@ -0,0 +1,166 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\Common\Collections\Expr\Value;
/**
* Builder for Expressions in the {@link Selectable} interface.
*
* Important Notice for interoperable code: You have to use scalar
* values only for comparisons, otherwise the behavior of the comparision
* may be different between implementations (Array vs ORM vs ODM).
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class ExpressionBuilder
{
/**
* @param mixed $x
*
* @return CompositeExpression
*/
public function andX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
}
/**
* @param mixed $x
*
* @return CompositeExpression
*/
public function orX($x = null)
{
return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args());
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function eq($field, $value)
{
return new Comparison($field, Comparison::EQ, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function gt($field, $value)
{
return new Comparison($field, Comparison::GT, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function lt($field, $value)
{
return new Comparison($field, Comparison::LT, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function gte($field, $value)
{
return new Comparison($field, Comparison::GTE, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function lte($field, $value)
{
return new Comparison($field, Comparison::LTE, new Value($value));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function neq($field, $value)
{
return new Comparison($field, Comparison::NEQ, new Value($value));
}
/**
* @param string $field
*
* @return Comparison
*/
public function isNull($field)
{
return new Comparison($field, Comparison::EQ, new Value(null));
}
/**
* @param string $field
* @param mixed $values
*
* @return Comparison
*/
public function in($field, array $values)
{
return new Comparison($field, Comparison::IN, new Value($values));
}
/**
* @param string $field
* @param mixed $values
*
* @return Comparison
*/
public function notIn($field, array $values)
{
return new Comparison($field, Comparison::NIN, new Value($values));
}
/**
* @param string $field
* @param mixed $value
*
* @return Comparison
*/
public function contains($field, $value)
{
return new Comparison($field, Comparison::CONTAINS, new Value($value));
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Collections;
/**
* Interface for collections that allow efficient filtering with an expression API.
*
* Goal of this interface is a backend independent method to fetch elements
* from a collections. {@link Expression} is crafted in a way that you can
* implement queries from both in-memory and database-backed collections.
*
* For database backed collections this allows very efficient access by
* utilizing the query APIs, for example SQL in the ORM. Applications using
* this API can implement efficient database access without having to ask the
* EntityManager or Repositories.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
interface Selectable
{
/**
* Selects all elements from a selectable that match the expression and
* returns a new collection containing these elements.
*
* @param Criteria $criteria
*
* @return Collection
*/
public function matching(Criteria $criteria);
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/Doctrine/Tests/TestInit.php"
>
<testsuites>
<testsuite name="Doctrine Collections Test Suite">
<directory>./tests/Doctrine/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./lib/Doctrine/</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
</exclude>
</groups>
</phpunit>

View File

@@ -0,0 +1,20 @@
<?php
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Tests\LazyArrayCollection;
class AbstractLazyCollectionTest extends \PHPUnit_Framework_TestCase
{
public function testLazyCollection()
{
$collection = new LazyArrayCollection();
$this->assertFalse($collection->isInitialized());
$this->assertCount(3, $collection);
$collection->add('bar');
$this->assertTrue($collection->isInitialized());
$this->assertCount(4, $collection);
}
}

View File

@@ -0,0 +1,296 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
/**
* Tests for {@see \Doctrine\Common\Collections\ArrayCollection}
*
* @covers \Doctrine\Common\Collections\ArrayCollection
*/
class ArrayCollectionTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideDifferentElements
*/
public function testToArray($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame($elements, $collection->toArray());
}
/**
* @dataProvider provideDifferentElements
*/
public function testFirst($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(reset($elements), $collection->first());
}
/**
* @dataProvider provideDifferentElements
*/
public function testLast($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(end($elements), $collection->last());
}
/**
* @dataProvider provideDifferentElements
*/
public function testKey($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(key($elements), $collection->key());
next($elements);
$collection->next();
$this->assertSame(key($elements), $collection->key());
}
/**
* @dataProvider provideDifferentElements
*/
public function testNext($elements)
{
$collection = new ArrayCollection($elements);
while (true) {
$collectionNext = $collection->next();
$arrayNext = next($elements);
if(!$collectionNext || !$arrayNext) {
break;
}
$this->assertSame($arrayNext, $collectionNext, "Returned value of ArrayCollection::next() and next() not match");
$this->assertSame(key($elements), $collection->key(), "Keys not match");
$this->assertSame(current($elements), $collection->current(), "Current values not match");
}
}
/**
* @dataProvider provideDifferentElements
*/
public function testCurrent($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(current($elements), $collection->current());
next($elements);
$collection->next();
$this->assertSame(current($elements), $collection->current());
}
/**
* @dataProvider provideDifferentElements
*/
public function testGetKeys($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(array_keys($elements), $collection->getKeys());
}
/**
* @dataProvider provideDifferentElements
*/
public function testGetValues($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(array_values($elements), $collection->getValues());
}
/**
* @dataProvider provideDifferentElements
*/
public function testCount($elements)
{
$collection = new ArrayCollection($elements);
$this->assertSame(count($elements), $collection->count());
}
/**
* @dataProvider provideDifferentElements
*/
public function testIterator($elements)
{
$collection = new ArrayCollection($elements);
$iterations = 0;
foreach($collection->getIterator() as $key => $item) {
$this->assertSame($elements[$key], $item, "Item {$key} not match");
$iterations++;
}
$this->assertEquals(count($elements), $iterations, "Number of iterations not match");
}
/**
* @return array
*/
public function provideDifferentElements()
{
return array(
'indexed' => array(array(1, 2, 3, 4, 5)),
'associative' => array(array('A' => 'a', 'B' => 'b', 'C' => 'c')),
'mixed' => array(array('A' => 'a', 1, 'B' => 'b', 2, 3)),
);
}
public function testRemove()
{
$elements = array(1, 'A' => 'a', 2, 'B' => 'b', 3);
$collection = new ArrayCollection($elements);
$this->assertEquals(1, $collection->remove(0));
unset($elements[0]);
$this->assertEquals(null, $collection->remove('non-existent'));
unset($elements['non-existent']);
$this->assertEquals(2, $collection->remove(1));
unset($elements[1]);
$this->assertEquals('a', $collection->remove('A'));
unset($elements['A']);
$this->assertEquals($elements, $collection->toArray());
}
public function testRemoveElement()
{
$elements = array(1, 'A' => 'a', 2, 'B' => 'b', 3, 'A2' => 'a', 'B2' => 'b');
$collection = new ArrayCollection($elements);
$this->assertTrue($collection->removeElement(1));
unset($elements[0]);
$this->assertFalse($collection->removeElement('non-existent'));
$this->assertTrue($collection->removeElement('a'));
unset($elements['A']);
$this->assertTrue($collection->removeElement('a'));
unset($elements['A2']);
$this->assertEquals($elements, $collection->toArray());
}
public function testContainsKey()
{
$elements = array(1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'B2' => 'b');
$collection = new ArrayCollection($elements);
$this->assertTrue($collection->containsKey(0), "Contains index 0");
$this->assertTrue($collection->containsKey('A'), "Contains key \"A\"");
$this->assertTrue($collection->containsKey('null'), "Contains key \"null\", with value null");
$this->assertFalse($collection->containsKey('non-existent'), "Doesn't contain key");
}
public function testEmpty()
{
$collection = new ArrayCollection();
$this->assertTrue($collection->isEmpty(), "Empty collection");
$collection->add(1);
$this->assertFalse($collection->isEmpty(), "Not empty collection");
}
public function testContains()
{
$elements = array(1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0);
$collection = new ArrayCollection($elements);
$this->assertTrue($collection->contains(0), "Contains Zero");
$this->assertTrue($collection->contains('a'), "Contains \"a\"");
$this->assertTrue($collection->contains(null), "Contains Null");
$this->assertFalse($collection->contains('non-existent'), "Doesn't contain an element");
}
public function testExists()
{
$elements = array(1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0);
$collection = new ArrayCollection($elements);
$this->assertTrue($collection->exists(function($key, $element) {
return $key == 'A' && $element == 'a';
}), "Element exists");
$this->assertFalse($collection->exists(function($key, $element) {
return $key == 'non-existent' && $element == 'non-existent';
}), "Element not exists");
}
public function testIndexOf()
{
$elements = array(1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0);
$collection = new ArrayCollection($elements);
$this->assertSame(array_search(2, $elements, true), $collection->indexOf(2), 'Index of 2');
$this->assertSame(array_search(null, $elements, true), $collection->indexOf(null), 'Index of null');
$this->assertSame(array_search('non-existent', $elements, true), $collection->indexOf('non-existent'), 'Index of non existent');
}
public function testGet()
{
$elements = array(1, 'A' => 'a', 2, 'null' => null, 3, 'A2' => 'a', 'zero' => 0);
$collection = new ArrayCollection($elements);
$this->assertSame(2, $collection->get(1), 'Get element by index');
$this->assertSame('a', $collection->get('A'), 'Get element by name');
$this->assertSame(null, $collection->get('non-existent'), 'Get non existent element');
}
public function testMatchingWithSortingPreservesyKeys()
{
$object1 = new \stdClass();
$object2 = new \stdClass();
$object1->sortField = 2;
$object2->sortField = 1;
$collection = new ArrayCollection(array(
'object1' => $object1,
'object2' => $object2,
));
$this->assertSame(
array(
'object2' => $object2,
'object1' => $object1,
),
$collection
->matching(new Criteria(null, array('sortField' => Criteria::ASC)))
->toArray()
);
}
}

View File

@@ -0,0 +1,250 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
use Doctrine\Common\Collections\ExpressionBuilder;
/**
* @group DDC-1637
*/
class ClosureExpressionVisitorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ClosureExpressionVisitor
*/
private $visitor;
/**
* @var ExpressionBuilder
*/
private $builder;
protected function setUp()
{
$this->visitor = new ClosureExpressionVisitor();
$this->builder = new ExpressionBuilder();
}
public function testGetObjectFieldValueIsAccessor()
{
$object = new TestObject(1, 2, true);
$this->assertTrue($this->visitor->getObjectFieldValue($object, 'baz'));
}
public function testGetObjectFieldValueMagicCallMethod()
{
$object = new TestObject(1, 2, true, 3);
$this->assertEquals(3, $this->visitor->getObjectFieldValue($object, 'qux'));
}
public function testWalkEqualsComparison()
{
$closure = $this->visitor->walkComparison($this->builder->eq("foo", 1));
$this->assertTrue($closure(new TestObject(1)));
$this->assertFalse($closure(new TestObject(2)));
}
public function testWalkNotEqualsComparison()
{
$closure = $this->visitor->walkComparison($this->builder->neq("foo", 1));
$this->assertFalse($closure(new TestObject(1)));
$this->assertTrue($closure(new TestObject(2)));
}
public function testWalkLessThanComparison()
{
$closure = $this->visitor->walkComparison($this->builder->lt("foo", 1));
$this->assertFalse($closure(new TestObject(1)));
$this->assertTrue($closure(new TestObject(0)));
}
public function testWalkLessThanEqualsComparison()
{
$closure = $this->visitor->walkComparison($this->builder->lte("foo", 1));
$this->assertFalse($closure(new TestObject(2)));
$this->assertTrue($closure(new TestObject(1)));
$this->assertTrue($closure(new TestObject(0)));
}
public function testWalkGreaterThanEqualsComparison()
{
$closure = $this->visitor->walkComparison($this->builder->gte("foo", 1));
$this->assertTrue($closure(new TestObject(2)));
$this->assertTrue($closure(new TestObject(1)));
$this->assertFalse($closure(new TestObject(0)));
}
public function testWalkGreaterThanComparison()
{
$closure = $this->visitor->walkComparison($this->builder->gt("foo", 1));
$this->assertTrue($closure(new TestObject(2)));
$this->assertFalse($closure(new TestObject(1)));
$this->assertFalse($closure(new TestObject(0)));
}
public function testWalkInComparison()
{
$closure = $this->visitor->walkComparison($this->builder->in("foo", array(1, 2, 3)));
$this->assertTrue($closure(new TestObject(2)));
$this->assertTrue($closure(new TestObject(1)));
$this->assertFalse($closure(new TestObject(0)));
}
public function testWalkNotInComparison()
{
$closure = $this->visitor->walkComparison($this->builder->notIn("foo", array(1, 2, 3)));
$this->assertFalse($closure(new TestObject(1)));
$this->assertFalse($closure(new TestObject(2)));
$this->assertTrue($closure(new TestObject(0)));
$this->assertTrue($closure(new TestObject(4)));
}
public function testWalkContainsComparison()
{
$closure = $this->visitor->walkComparison($this->builder->contains('foo', 'hello'));
$this->assertTrue($closure(new TestObject('hello world')));
$this->assertFalse($closure(new TestObject('world')));
}
public function testWalkAndCompositeExpression()
{
$closure = $this->visitor->walkCompositeExpression(
$this->builder->andX(
$this->builder->eq("foo", 1),
$this->builder->eq("bar", 1)
)
);
$this->assertTrue($closure(new TestObject(1, 1)));
$this->assertFalse($closure(new TestObject(1, 0)));
$this->assertFalse($closure(new TestObject(0, 1)));
$this->assertFalse($closure(new TestObject(0, 0)));
}
public function testWalkOrCompositeExpression()
{
$closure = $this->visitor->walkCompositeExpression(
$this->builder->orX(
$this->builder->eq("foo", 1),
$this->builder->eq("bar", 1)
)
);
$this->assertTrue($closure(new TestObject(1, 1)));
$this->assertTrue($closure(new TestObject(1, 0)));
$this->assertTrue($closure(new TestObject(0, 1)));
$this->assertFalse($closure(new TestObject(0, 0)));
}
public function testSortByFieldAscending()
{
$objects = array(new TestObject("b"), new TestObject("a"), new TestObject("c"));
$sort = ClosureExpressionVisitor::sortByField("foo");
usort($objects, $sort);
$this->assertEquals("a", $objects[0]->getFoo());
$this->assertEquals("b", $objects[1]->getFoo());
$this->assertEquals("c", $objects[2]->getFoo());
}
public function testSortByFieldDescending()
{
$objects = array(new TestObject("b"), new TestObject("a"), new TestObject("c"));
$sort = ClosureExpressionVisitor::sortByField("foo", -1);
usort($objects, $sort);
$this->assertEquals("c", $objects[0]->getFoo());
$this->assertEquals("b", $objects[1]->getFoo());
$this->assertEquals("a", $objects[2]->getFoo());
}
public function testSortDelegate()
{
$objects = array(new TestObject("a", "c"), new TestObject("a", "b"), new TestObject("a", "a"));
$sort = ClosureExpressionVisitor::sortByField("bar", 1);
$sort = ClosureExpressionVisitor::sortByField("foo", 1, $sort);
usort($objects, $sort);
$this->assertEquals("a", $objects[0]->getBar());
$this->assertEquals("b", $objects[1]->getBar());
$this->assertEquals("c", $objects[2]->getBar());
}
public function testArrayComparison()
{
$closure = $this->visitor->walkComparison($this->builder->eq("foo", 42));
$this->assertTrue($closure(array('foo' => 42)));
}
}
class TestObject
{
private $foo;
private $bar;
private $baz;
private $qux;
public function __construct($foo = null, $bar = null, $baz = null, $qux = null)
{
$this->foo = $foo;
$this->bar = $bar;
$this->baz = $baz;
$this->qux = $qux;
}
public function __call($name, $arguments)
{
if ('getqux' === $name) {
return $this->qux;
}
}
public function getFoo()
{
return $this->foo;
}
public function getBar()
{
return $this->bar;
}
public function isBaz()
{
return $this->baz;
}
}

View File

@@ -0,0 +1,265 @@
<?php
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
class CollectionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Collection
*/
private $collection;
protected function setUp()
{
$this->collection = new ArrayCollection();
}
public function testIssetAndUnset()
{
$this->assertFalse(isset($this->collection[0]));
$this->collection->add('testing');
$this->assertTrue(isset($this->collection[0]));
unset($this->collection[0]);
$this->assertFalse(isset($this->collection[0]));
}
public function testToString()
{
$this->collection->add('testing');
$this->assertTrue(is_string((string) $this->collection));
}
public function testRemovingNonExistentEntryReturnsNull()
{
$this->assertEquals(null, $this->collection->remove('testing_does_not_exist'));
}
public function testExists()
{
$this->collection->add("one");
$this->collection->add("two");
$exists = $this->collection->exists(function($k, $e) { return $e == "one"; });
$this->assertTrue($exists);
$exists = $this->collection->exists(function($k, $e) { return $e == "other"; });
$this->assertFalse($exists);
}
public function testMap()
{
$this->collection->add(1);
$this->collection->add(2);
$res = $this->collection->map(function($e) { return $e * 2; });
$this->assertEquals(array(2, 4), $res->toArray());
}
public function testFilter()
{
$this->collection->add(1);
$this->collection->add("foo");
$this->collection->add(3);
$res = $this->collection->filter(function($e) { return is_numeric($e); });
$this->assertEquals(array(0 => 1, 2 => 3), $res->toArray());
}
public function testFirstAndLast()
{
$this->collection->add('one');
$this->collection->add('two');
$this->assertEquals($this->collection->first(), 'one');
$this->assertEquals($this->collection->last(), 'two');
}
public function testArrayAccess()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertEquals($this->collection[0], 'one');
$this->assertEquals($this->collection[1], 'two');
unset($this->collection[0]);
$this->assertEquals($this->collection->count(), 1);
}
public function testContainsKey()
{
$this->collection[5] = 'five';
$this->assertTrue($this->collection->containsKey(5));
}
public function testContains()
{
$this->collection[0] = 'test';
$this->assertTrue($this->collection->contains('test'));
}
public function testSearch()
{
$this->collection[0] = 'test';
$this->assertEquals(0, $this->collection->indexOf('test'));
}
public function testGet()
{
$this->collection[0] = 'test';
$this->assertEquals('test', $this->collection->get(0));
}
public function testGetKeys()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertEquals(array(0, 1), $this->collection->getKeys());
}
public function testGetValues()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertEquals(array('one', 'two'), $this->collection->getValues());
}
public function testCount()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertEquals($this->collection->count(), 2);
$this->assertEquals(count($this->collection), 2);
}
public function testForAll()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertEquals($this->collection->forAll(function($k, $e) { return is_string($e); }), true);
$this->assertEquals($this->collection->forAll(function($k, $e) { return is_array($e); }), false);
}
public function testPartition()
{
$this->collection[] = true;
$this->collection[] = false;
$partition = $this->collection->partition(function($k, $e) { return $e == true; });
$this->assertEquals($partition[0][0], true);
$this->assertEquals($partition[1][0], false);
}
public function testClear()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->collection->clear();
$this->assertEquals($this->collection->isEmpty(), true);
}
public function testRemove()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$el = $this->collection->remove(0);
$this->assertEquals('one', $el);
$this->assertEquals($this->collection->contains('one'), false);
$this->assertNull($this->collection->remove(0));
}
public function testRemoveElement()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->assertTrue($this->collection->removeElement('two'));
$this->assertFalse($this->collection->contains('two'));
$this->assertFalse($this->collection->removeElement('two'));
}
public function testSlice()
{
$this->collection[] = 'one';
$this->collection[] = 'two';
$this->collection[] = 'three';
$slice = $this->collection->slice(0, 1);
$this->assertInternalType('array', $slice);
$this->assertEquals(array('one'), $slice);
$slice = $this->collection->slice(1);
$this->assertEquals(array(1 => 'two', 2 => 'three'), $slice);
$slice = $this->collection->slice(1, 1);
$this->assertEquals(array(1 => 'two'), $slice);
}
public function fillMatchingFixture()
{
$std1 = new \stdClass();
$std1->foo = "bar";
$this->collection[] = $std1;
$std2 = new \stdClass();
$std2->foo = "baz";
$this->collection[] = $std2;
}
/**
* @group DDC-1637
*/
public function testMatching()
{
$this->fillMatchingFixture();
$col = $this->collection->matching(new Criteria(Criteria::expr()->eq("foo", "bar")));
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col);
$this->assertNotSame($col, $this->collection);
$this->assertEquals(1, count($col));
}
/**
* @group DDC-1637
*/
public function testMatchingOrdering()
{
$this->fillMatchingFixture();
$col = $this->collection->matching(new Criteria(null, array('foo' => 'DESC')));
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col);
$this->assertNotSame($col, $this->collection);
$this->assertEquals(2, count($col));
$this->assertEquals('baz', $col->first()->foo);
$this->assertEquals('bar', $col->last()->foo);
}
/**
* @group DDC-1637
*/
public function testMatchingSlice()
{
$this->fillMatchingFixture();
$col = $this->collection->matching(new Criteria(null, null, 1, 1));
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col);
$this->assertNotSame($col, $this->collection);
$this->assertEquals(1, count($col));
$this->assertEquals('baz', $col[0]->foo);
}
public function testCanRemoveNullValuesByKey()
{
$this->collection->add(null);
$this->collection->remove(0);
$this->assertTrue($this->collection->isEmpty());
}
public function testCanVerifyExistingKeysWithNullValues()
{
$this->collection->set('key', null);
$this->assertTrue($this->collection->containsKey('key'));
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\CompositeExpression;
class CriteriaTest extends \PHPUnit_Framework_TestCase
{
public function testCreate()
{
$criteria = Criteria::create();
$this->assertInstanceOf('Doctrine\Common\Collections\Criteria', $criteria);
}
public function testConstructor()
{
$expr = new Comparison("field", "=", "value");
$criteria = new Criteria($expr, array("foo" => "ASC"), 10, 20);
$this->assertSame($expr, $criteria->getWhereExpression());
$this->assertEquals(array("foo" => "ASC"), $criteria->getOrderings());
$this->assertEquals(10, $criteria->getFirstResult());
$this->assertEquals(20, $criteria->getMaxResults());
}
public function testWhere()
{
$expr = new Comparison("field", "=", "value");
$criteria = new Criteria();
$criteria->where($expr);
$this->assertSame($expr, $criteria->getWhereExpression());
}
public function testAndWhere()
{
$expr = new Comparison("field", "=", "value");
$criteria = new Criteria();
$criteria->where($expr);
$expr = $criteria->getWhereExpression();
$criteria->andWhere($expr);
$where = $criteria->getWhereExpression();
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $where);
$this->assertEquals(CompositeExpression::TYPE_AND, $where->getType());
$this->assertSame(array($expr, $expr), $where->getExpressionList());
}
public function testOrWhere()
{
$expr = new Comparison("field", "=", "value");
$criteria = new Criteria();
$criteria->where($expr);
$expr = $criteria->getWhereExpression();
$criteria->orWhere($expr);
$where = $criteria->getWhereExpression();
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $where);
$this->assertEquals(CompositeExpression::TYPE_OR, $where->getType());
$this->assertSame(array($expr, $expr), $where->getExpressionList());
}
public function testOrderings()
{
$criteria = Criteria::create()
->orderBy(array("foo" => "ASC"));
$this->assertEquals(array("foo" => "ASC"), $criteria->getOrderings());
}
public function testExpr()
{
$this->assertInstanceOf('Doctrine\Common\Collections\ExpressionBuilder', Criteria::expr());
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Doctrine\Tests\Common\Collections;
use Doctrine\Common\Collections\ExpressionBuilder;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\CompositeExpression;
/**
* @group DDC-1637
*/
class ExpressionBuilderTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ExpressionBuilder
*/
private $builder;
protected function setUp()
{
$this->builder = new ExpressionBuilder();
}
public function testAndX()
{
$expr = $this->builder->andX($this->builder->eq("a", "b"));
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $expr);
$this->assertEquals(CompositeExpression::TYPE_AND, $expr->getType());
}
public function testOrX()
{
$expr = $this->builder->orX($this->builder->eq("a", "b"));
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $expr);
$this->assertEquals(CompositeExpression::TYPE_OR, $expr->getType());
}
public function testInvalidAndXArgument()
{
$this->setExpectedException("RuntimeException");
$this->builder->andX("foo");
}
public function testEq()
{
$expr = $this->builder->eq("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::EQ, $expr->getOperator());
}
public function testNeq()
{
$expr = $this->builder->neq("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::NEQ, $expr->getOperator());
}
public function testLt()
{
$expr = $this->builder->lt("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::LT, $expr->getOperator());
}
public function testGt()
{
$expr = $this->builder->gt("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::GT, $expr->getOperator());
}
public function testGte()
{
$expr = $this->builder->gte("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::GTE, $expr->getOperator());
}
public function testLte()
{
$expr = $this->builder->lte("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::LTE, $expr->getOperator());
}
public function testIn()
{
$expr = $this->builder->in("a", array("b"));
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::IN, $expr->getOperator());
}
public function testNotIn()
{
$expr = $this->builder->notIn("a", array("b"));
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::NIN, $expr->getOperator());
}
public function testIsNull()
{
$expr = $this->builder->isNull("a");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::EQ, $expr->getOperator());
}
public function testContains()
{
$expr = $this->builder->contains("a", "b");
$this->assertInstanceOf('Doctrine\Common\Collections\Expr\Comparison', $expr);
$this->assertEquals(Comparison::CONTAINS, $expr->getOperator());
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Doctrine\Tests;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Simple lazy collection that used an ArrayCollection as backed collection
*/
class LazyArrayCollection extends AbstractLazyCollection
{
/**
* Do the initialization logic
*
* @return void
*/
protected function doInitialize()
{
$this->collection = new ArrayCollection(array('a', 'b', 'c'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file bootstraps the test environment.
*/
namespace Doctrine\Tests;
error_reporting(E_ALL | E_STRICT);
// register silently failing autoloader
spl_autoload_register(function($class) {
if (0 === strpos($class, 'Doctrine\Tests\\')) {
$path = __DIR__.'/../../'.strtr($class, '\\', '/').'.php';
if (is_file($path) && is_readable($path)) {
require_once $path;
return true;
}
}
});
require_once __DIR__ . "/../../../vendor/autoload.php";

View File

@@ -0,0 +1,5 @@
phpunit.xml
composer.lock
build
vendor
coverage.clover

View File

@@ -0,0 +1,46 @@
before_commands:
- "composer install --prefer-source"
tools:
external_code_coverage:
timeout: 600
php_code_coverage:
enabled: true
test_command: ./vendor/bin/phpunit
php_code_sniffer:
enabled: true
config:
standard: PSR2
filter:
paths: ["src/*", "tests/*"]
php_cpd:
enabled: true
excluded_dirs: ["build/*", "tests", "vendor"]
php_cs_fixer:
enabled: true
config:
level: all
filter:
paths: ["src/*", "tests/*"]
php_loc:
enabled: true
excluded_dirs: ["build", "tests", "vendor"]
php_mess_detector:
enabled: true
config:
ruleset: phpmd.xml.dist
design_rules: { eval_expression: false }
filter:
paths: ["src/*"]
php_pdepend:
enabled: true
excluded_dirs: ["build", "tests", "vendor"]
php_analyzer:
enabled: true
filter:
paths: ["src/*", "tests/*"]
php_hhvm:
enabled: true
filter:
paths: ["src/*", "tests/*"]
sensiolabs_security_checker: true

View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -x
if [ "$TRAVIS_PHP_VERSION" = 'hhvm' ] || [ "$TRAVIS_PHP_VERSION" = 'hhvm-nightly' ] ; then
curl -sS https://getcomposer.org/installer > composer-installer.php
hhvm composer-installer.php
hhvm -v ResourceLimit.SocketDefaultTimeout=30 -v Http.SlowQueryThreshold=30000 composer.phar update --prefer-source
elif [ "$TRAVIS_PHP_VERSION" = '5.3.3' ] ; then
composer self-update
composer update --prefer-source --no-dev
composer dump-autoload
else
composer self-update
composer update --prefer-source
fi

View File

@@ -0,0 +1,22 @@
language: php
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
before_script:
- ./.travis.install.sh
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then PHPUNIT_FLAGS="--coverage-clover coverage.clover"; else PHPUNIT_FLAGS=""; fi
script:
- if [ $TRAVIS_PHP_VERSION = '5.3.3' ]; then phpunit; fi
- if [ $TRAVIS_PHP_VERSION != '5.3.3' ]; then ./vendor/bin/phpunit $PHPUNIT_FLAGS; fi
- if [ $TRAVIS_PHP_VERSION != '5.3.3' ]; then ./vendor/bin/phpcs --standard=PSR2 ./src/ ./tests/; fi
- if [[ $TRAVIS_PHP_VERSION != '5.3.3' && $TRAVIS_PHP_VERSION != '5.4.29' && $TRAVIS_PHP_VERSION != '5.5.13' ]]; then php -n ./vendor/bin/athletic -p ./tests/DoctrineTest/InstantiatorPerformance/ -f GroupedFormatter; fi
after_script:
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi

View File

@@ -0,0 +1,35 @@
# Contributing
* Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
* The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)
* Any contribution must provide tests for additional introduced conditions
* Any un-confirmed issue needs a failing test case before being accepted
* Pull requests must be sent from a new hotfix/feature branch, not from `master`.
## Installation
To install the project and run the tests, you need to clone it first:
```sh
$ git clone git://github.com/doctrine/instantiator.git
```
You will then need to run a composer installation:
```sh
$ cd Instantiator
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar update
```
## Testing
The PHPUnit version to be used is the one installed as a dev- dependency via composer:
```sh
$ ./vendor/bin/phpunit
```
Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement
won't be merged.

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,40 @@
# Instantiator
This library provides a way of avoiding usage of constructors when instantiating PHP classes.
[![Build Status](https://travis-ci.org/doctrine/instantiator.svg?branch=master)](https://travis-ci.org/doctrine/instantiator)
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/instantiator/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/instantiator/?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/instantiator/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/instantiator/?branch=master)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--instantiator/badge.svg)](https://www.versioneye.com/package/php--doctrine--instantiator)
[![HHVM Status](http://hhvm.h4cc.de/badge/doctrine/instantiator.png)](http://hhvm.h4cc.de/package/doctrine/instantiator)
[![Latest Stable Version](https://poser.pugx.org/doctrine/instantiator/v/stable.png)](https://packagist.org/packages/doctrine/instantiator)
[![Latest Unstable Version](https://poser.pugx.org/doctrine/instantiator/v/unstable.png)](https://packagist.org/packages/doctrine/instantiator)
## Installation
The suggested installation method is via [composer](https://getcomposer.org/):
```sh
php composer.phar require "doctrine/instantiator:~1.0.3"
```
## Usage
The instantiator is able to create new instances of any class without using the constructor or any API of the class
itself:
```php
$instantiator = new \Doctrine\Instantiator\Instantiator();
$instance = $instantiator->instantiate('My\\ClassName\\Here');
```
## Contributing
Please read the [CONTRIBUTING.md](CONTRIBUTING.md) contents if you wish to help out!
## Credits
This library was migrated from [ocramius/instantiator](https://github.com/Ocramius/Instantiator), which
has been donated to the doctrine organization, and which is now deprecated in favour of this package.

View File

@@ -0,0 +1,45 @@
{
"name": "doctrine/instantiator",
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"type": "library",
"license": "MIT",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"instantiate",
"constructor"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"ext-phar": "*",
"ext-pdo": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0",
"athletic/athletic": "~0.1.8"
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"autoload-dev": {
"psr-0": {
"DoctrineTest\\InstantiatorPerformance\\": "tests",
"DoctrineTest\\InstantiatorTest\\": "tests",
"DoctrineTest\\InstantiatorTestAsset\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ruleset
name="Instantiator rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"
>
<rule ref="rulesets/cleancode.xml">
<!-- static access is used for caching purposes -->
<exclude name="StaticAccess"/>
</rule>
<rule ref="rulesets/codesize.xml"/>
<rule ref="rulesets/controversial.xml"/>
<rule ref="rulesets/design.xml"/>
<rule ref="rulesets/naming.xml"/>
<rule ref="rulesets/unusedcode.xml"/>
<rule
name="NPathComplexity"
message="The {0} {1}() has an NPath complexity of {2}. The configured NPath complexity threshold is {3}."
class="PHP_PMD_Rule_Design_NpathComplexity"
>
<properties>
<property name="minimum" description="The npath reporting threshold" value="10"/>
</properties>
</rule>
</ruleset>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<phpunit
bootstrap="./vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
verbose="true"
stopOnFailure="false"
processIsolation="false"
backupGlobals="false"
syntaxCheck="true"
>
<testsuite name="Doctrine\Instantiator tests">
<directory>./tests/DoctrineTest/InstantiatorTest</directory>
</testsuite>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,29 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Instantiator\Exception;
/**
* Base exception marker interface for the instantiator component
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
interface ExceptionInterface
{
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Instantiator\Exception;
use InvalidArgumentException as BaseInvalidArgumentException;
use ReflectionClass;
/**
* Exception for invalid arguments provided to the instantiator
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
/**
* @param string $className
*
* @return self
*/
public static function fromNonExistingClass($className)
{
if (interface_exists($className)) {
return new self(sprintf('The provided type "%s" is an interface, and can not be instantiated', $className));
}
if (PHP_VERSION_ID >= 50400 && trait_exists($className)) {
return new self(sprintf('The provided type "%s" is a trait, and can not be instantiated', $className));
}
return new self(sprintf('The provided class "%s" does not exist', $className));
}
/**
* @param ReflectionClass $reflectionClass
*
* @return self
*/
public static function fromAbstractClass(ReflectionClass $reflectionClass)
{
return new self(sprintf(
'The provided class "%s" is abstract, and can not be instantiated',
$reflectionClass->getName()
));
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Instantiator\Exception;
use Exception;
use ReflectionClass;
use UnexpectedValueException as BaseUnexpectedValueException;
/**
* Exception for given parameters causing invalid/unexpected state on instantiation
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface
{
/**
* @param ReflectionClass $reflectionClass
* @param Exception $exception
*
* @return self
*/
public static function fromSerializationTriggeredException(ReflectionClass $reflectionClass, Exception $exception)
{
return new self(
sprintf(
'An exception was raised while trying to instantiate an instance of "%s" via un-serialization',
$reflectionClass->getName()
),
0,
$exception
);
}
/**
* @param ReflectionClass $reflectionClass
* @param string $errorString
* @param int $errorCode
* @param string $errorFile
* @param int $errorLine
*
* @return UnexpectedValueException
*/
public static function fromUncleanUnSerialization(
ReflectionClass $reflectionClass,
$errorString,
$errorCode,
$errorFile,
$errorLine
) {
return new self(
sprintf(
'Could not produce an instance of "%s" via un-serialization, since an error was triggered '
. 'in file "%s" at line "%d"',
$reflectionClass->getName(),
$errorFile,
$errorLine
),
0,
new Exception($errorString, $errorCode)
);
}
}

View File

@@ -0,0 +1,273 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Instantiator;
use Closure;
use Doctrine\Instantiator\Exception\InvalidArgumentException;
use Doctrine\Instantiator\Exception\UnexpectedValueException;
use Exception;
use ReflectionClass;
/**
* {@inheritDoc}
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
final class Instantiator implements InstantiatorInterface
{
/**
* Markers used internally by PHP to define whether {@see \unserialize} should invoke
* the method {@see \Serializable::unserialize()} when dealing with classes implementing
* the {@see \Serializable} interface.
*/
const SERIALIZATION_FORMAT_USE_UNSERIALIZER = 'C';
const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';
/**
* @var \Closure[] of {@see \Closure} instances used to instantiate specific classes
*/
private static $cachedInstantiators = array();
/**
* @var object[] of objects that can directly be cloned
*/
private static $cachedCloneables = array();
/**
* {@inheritDoc}
*/
public function instantiate($className)
{
if (isset(self::$cachedCloneables[$className])) {
return clone self::$cachedCloneables[$className];
}
if (isset(self::$cachedInstantiators[$className])) {
$factory = self::$cachedInstantiators[$className];
return $factory();
}
return $this->buildAndCacheFromFactory($className);
}
/**
* Builds the requested object and caches it in static properties for performance
*
* @param string $className
*
* @return object
*/
private function buildAndCacheFromFactory($className)
{
$factory = self::$cachedInstantiators[$className] = $this->buildFactory($className);
$instance = $factory();
if ($this->isSafeToClone(new ReflectionClass($instance))) {
self::$cachedCloneables[$className] = clone $instance;
}
return $instance;
}
/**
* Builds a {@see \Closure} capable of instantiating the given $className without
* invoking its constructor.
*
* @param string $className
*
* @return Closure
*/
private function buildFactory($className)
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return function () use ($reflectionClass) {
return $reflectionClass->newInstanceWithoutConstructor();
};
}
$serializedString = sprintf(
'%s:%d:"%s":0:{}',
$this->getSerializationFormat($reflectionClass),
strlen($className),
$className
);
$this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
return function () use ($serializedString) {
return unserialize($serializedString);
};
}
/**
* @param string $className
*
* @return ReflectionClass
*
* @throws InvalidArgumentException
*/
private function getReflectionClass($className)
{
if (! class_exists($className)) {
throw InvalidArgumentException::fromNonExistingClass($className);
}
$reflection = new ReflectionClass($className);
if ($reflection->isAbstract()) {
throw InvalidArgumentException::fromAbstractClass($reflection);
}
return $reflection;
}
/**
* @param ReflectionClass $reflectionClass
* @param string $serializedString
*
* @throws UnexpectedValueException
*
* @return void
*/
private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, $serializedString)
{
set_error_handler(function ($code, $message, $file, $line) use ($reflectionClass, & $error) {
$error = UnexpectedValueException::fromUncleanUnSerialization(
$reflectionClass,
$message,
$code,
$file,
$line
);
});
$this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
restore_error_handler();
if ($error) {
throw $error;
}
}
/**
* @param ReflectionClass $reflectionClass
* @param string $serializedString
*
* @throws UnexpectedValueException
*
* @return void
*/
private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, $serializedString)
{
try {
unserialize($serializedString);
} catch (Exception $exception) {
restore_error_handler();
throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
}
}
/**
* @param ReflectionClass $reflectionClass
*
* @return bool
*/
private function isInstantiableViaReflection(ReflectionClass $reflectionClass)
{
if (\PHP_VERSION_ID >= 50600) {
return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
}
return \PHP_VERSION_ID >= 50400 && ! $this->hasInternalAncestors($reflectionClass);
}
/**
* Verifies whether the given class is to be considered internal
*
* @param ReflectionClass $reflectionClass
*
* @return bool
*/
private function hasInternalAncestors(ReflectionClass $reflectionClass)
{
do {
if ($reflectionClass->isInternal()) {
return true;
}
} while ($reflectionClass = $reflectionClass->getParentClass());
return false;
}
/**
* Verifies if the given PHP version implements the `Serializable` interface serialization
* with an incompatible serialization format. If that's the case, use serialization marker
* "C" instead of "O".
*
* @link http://news.php.net/php.internals/74654
*
* @param ReflectionClass $reflectionClass
*
* @return string the serialization format marker, either self::SERIALIZATION_FORMAT_USE_UNSERIALIZER
* or self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER
*/
private function getSerializationFormat(ReflectionClass $reflectionClass)
{
if ($this->isPhpVersionWithBrokenSerializationFormat()
&& $reflectionClass->implementsInterface('Serializable')
) {
return self::SERIALIZATION_FORMAT_USE_UNSERIALIZER;
}
return self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER;
}
/**
* Checks whether the current PHP runtime uses an incompatible serialization format
*
* @return bool
*/
private function isPhpVersionWithBrokenSerializationFormat()
{
return PHP_VERSION_ID === 50429 || PHP_VERSION_ID === 50513;
}
/**
* Checks if a class is cloneable
*
* @param ReflectionClass $reflection
*
* @return bool
*/
private function isSafeToClone(ReflectionClass $reflection)
{
if (method_exists($reflection, 'isCloneable') && ! $reflection->isCloneable()) {
return false;
}
// not cloneable if it implements `__clone`, as we want to avoid calling it
return ! $reflection->hasMethod('__clone');
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Instantiator;
/**
* Instantiator provides utility methods to build objects without invoking their constructors
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
interface InstantiatorInterface
{
/**
* @param string $className
*
* @return object
*
* @throws \Doctrine\Instantiator\Exception\ExceptionInterface
*/
public function instantiate($className);
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace DoctrineTest\InstantiatorPerformance;
use Athletic\AthleticEvent;
use Doctrine\Instantiator\Instantiator;
/**
* Performance tests for {@see \Doctrine\Instantiator\Instantiator}
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
class InstantiatorPerformanceEvent extends AthleticEvent
{
/**
* @var \Doctrine\Instantiator\Instantiator
*/
private $instantiator;
/**
* {@inheritDoc}
*/
protected function setUp()
{
$this->instantiator = new Instantiator();
$this->instantiator->instantiate(__CLASS__);
$this->instantiator->instantiate('ArrayObject');
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SimpleSerializableAsset');
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset');
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\UnCloneableAsset');
}
/**
* @iterations 20000
* @baseline
* @group instantiation
*/
public function testInstantiateSelf()
{
$this->instantiator->instantiate(__CLASS__);
}
/**
* @iterations 20000
* @group instantiation
*/
public function testInstantiateInternalClass()
{
$this->instantiator->instantiate('ArrayObject');
}
/**
* @iterations 20000
* @group instantiation
*/
public function testInstantiateSimpleSerializableAssetClass()
{
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SimpleSerializableAsset');
}
/**
* @iterations 20000
* @group instantiation
*/
public function testInstantiateSerializableArrayObjectAsset()
{
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\SerializableArrayObjectAsset');
}
/**
* @iterations 20000
* @group instantiation
*/
public function testInstantiateUnCloneableAsset()
{
$this->instantiator->instantiate('DoctrineTest\\InstantiatorTestAsset\\UnCloneableAsset');
}
}

Some files were not shown because too many files have changed in this diff Show More