- Patch #1178246 by Crell: added Symfony2 HttpFoundation library to core.

8.0.x
Dries 2011-10-24 14:14:03 -04:00
parent d57f6e198a
commit c85d62c609
43 changed files with 6908 additions and 0 deletions

View File

@ -23,3 +23,4 @@ license, including:
jQuery - Copyright (c) 2008 - 2009 John Resig
Symfony2 - Copyright (c) 2004 - 2011 Fabien Potencier

View File

@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* ApcUniversalClassLoader implements a "universal" autoloader cached in APC for PHP 5.3.
*
* It is able to load classes that use either:
*
* * The technical interoperability standards for PHP 5.3 namespaces and
* class names (http://groups.google.com/group/php-standards/web/psr-0-final-proposal);
*
* * The PEAR naming convention for classes (http://pear.php.net/).
*
* Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be
* looked for in a list of locations to ease the vendoring of a sub-set of
* classes for large projects.
*
* Example usage:
*
* require 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
* require 'vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php';
*
* use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
*
* $loader = new ApcUniversalClassLoader('apc.prefix.');
*
* // register classes with namespaces
* $loader->registerNamespaces(array(
* 'Symfony\Component' => __DIR__.'/component',
* 'Symfony' => __DIR__.'/framework',
* 'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'),
* ));
*
* // register a library using the PEAR naming convention
* $loader->registerPrefixes(array(
* 'Swift_' => __DIR__.'/Swift',
* ));
*
* // activate the autoloader
* $loader->register();
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Kris Wallsmith <kris@symfony.com>
*
* @api
*/
class ApcUniversalClassLoader extends UniversalClassLoader
{
private $prefix;
/**
* Constructor.
*
* @param string $prefix A prefix to create a namespace in APC
*
* @api
*/
public function __construct($prefix)
{
if (!extension_loaded('apc')) {
throw new \RuntimeException('Unable to use ApcUniversalClassLoader as APC is not enabled.');
}
$this->prefix = $prefix;
}
/**
* Finds a file by class name while caching lookups to APC.
*
* @param string $class A class name to resolve to file
*/
public function findFile($class)
{
if (false === $file = apc_fetch($this->prefix.$class)) {
apc_store($this->prefix.$class, $file = parent::findFile($class));
}
return $file;
}
}

View File

@ -0,0 +1,222 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* ClassCollectionLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClassCollectionLoader
{
static private $loaded;
/**
* Loads a list of classes and caches them in one big file.
*
* @param array $classes An array of classes to load
* @param string $cacheDir A cache directory
* @param string $name The cache name prefix
* @param Boolean $autoReload Whether to flush the cache when the cache is stale or not
* @param Boolean $adaptive Whether to remove already declared classes or not
* @param string $extension File extension of the resulting file
*
* @throws \InvalidArgumentException When class can't be loaded
*/
static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php')
{
// each $name can only be loaded once per PHP process
if (isset(self::$loaded[$name])) {
return;
}
self::$loaded[$name] = true;
if ($adaptive) {
// don't include already declared classes
$classes = array_diff($classes, get_declared_classes(), get_declared_interfaces());
// the cache is different depending on which classes are already declared
$name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5);
}
$cache = $cacheDir.'/'.$name.$extension;
// auto-reload
$reload = false;
if ($autoReload) {
$metadata = $cacheDir.'/'.$name.$extension.'.meta';
if (!file_exists($metadata) || !file_exists($cache)) {
$reload = true;
} else {
$time = filemtime($cache);
$meta = unserialize(file_get_contents($metadata));
if ($meta[1] != $classes) {
$reload = true;
} else {
foreach ($meta[0] as $resource) {
if (!file_exists($resource) || filemtime($resource) > $time) {
$reload = true;
break;
}
}
}
}
}
if (!$reload && file_exists($cache)) {
require_once $cache;
return;
}
$files = array();
$content = '';
foreach ($classes as $class) {
if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
}
$r = new \ReflectionClass($class);
$files[] = $r->getFileName();
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName()));
// add namespace declaration for global code
if (!$r->inNamespace()) {
$c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n";
} else {
$c = self::fixNamespaceDeclarations('<?php '.$c);
$c = preg_replace('/^\s*<\?php/', '', $c);
}
$content .= $c;
}
// cache the core classes
if (!is_dir(dirname($cache))) {
mkdir(dirname($cache), 0777, true);
}
self::writeCacheFile($cache, '<?php '.$content);
if ($autoReload) {
// save the resources
self::writeCacheFile($metadata, serialize(array($files, $classes)));
}
}
/**
* Adds brackets around each namespace if it's not already the case.
*
* @param string $source Namespace string
*
* @return string Namespaces with brackets
*/
static public function fixNamespaceDeclarations($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
$inNamespace = false;
$tokens = token_get_all($source);
for ($i = 0, $max = count($tokens); $i < $max; $i++) {
$token = $tokens[$i];
if (is_string($token)) {
$output .= $token;
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
// strip comments
continue;
} elseif (T_NAMESPACE === $token[0]) {
if ($inNamespace) {
$output .= "}\n";
}
$output .= $token[1];
// namespace name and whitespaces
while (($t = $tokens[++$i]) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) {
$output .= $t[1];
}
if (is_string($t) && '{' === $t) {
$inNamespace = false;
--$i;
} else {
$output .= "\n{";
$inNamespace = true;
}
} else {
$output .= $token[1];
}
}
if ($inNamespace) {
$output .= "}\n";
}
return $output;
}
/**
* Writes a cache file.
*
* @param string $file Filename
* @param string $content Temporary file content
*
* @throws \RuntimeException when a cache file cannot be written
*/
static private function writeCacheFile($file, $content)
{
$tmpFile = tempnam(dirname($file), basename($file));
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
chmod($file, 0644);
return;
}
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
}
/**
* Removes comments from a PHP source string.
*
* We don't use the PHP php_strip_whitespace() function
* as we want the content to be readable and well-formatted.
*
* @param string $source A PHP string
*
* @return string The PHP string with the comments removed
*/
static private function stripComments($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
$output .= $token[1];
}
}
// replace multiple new lines with a single newline
$output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output);
return $output;
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* Checks that the class is actually declared in the included file.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugUniversalClassLoader extends UniversalClassLoader
{
/**
* Replaces all regular UniversalClassLoader instances by a DebugUniversalClassLoader ones.
*/
static public function enable()
{
if (!is_array($functions = spl_autoload_functions())) {
return;
}
foreach ($functions as $function) {
spl_autoload_unregister($function);
}
foreach ($functions as $function) {
if (is_array($function) && $function[0] instanceof UniversalClassLoader) {
$loader = new static();
$loader->registerNamespaceFallbacks($function[0]->getNamespaceFallbacks());
$loader->registerPrefixFallbacks($function[0]->getPrefixFallbacks());
$loader->registerNamespaces($function[0]->getNamespaces());
$loader->registerPrefixes($function[0]->getPrefixes());
$function[0] = $loader;
}
spl_autoload_register($function);
}
}
/**
* {@inheritDoc}
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
require $file;
if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) {
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
}
}
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2011 Fabien Potencier
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,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* A class loader that uses a mapping file to look up paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MapClassLoader
{
private $map = array();
/**
* Constructor.
*
* @param array $map A map where keys are classes and values the absolute file path
*/
public function __construct(array $map)
{
$this->map = $map;
}
/**
* Registers this instance as an autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
*/
public function loadClass($class)
{
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if (isset($this->map[$class])) {
require $this->map[$class];
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|null The path, if found
*/
public function findFile($class)
{
if ('\\' === $class[0]) {
$class = substr($class, 1);
}
if (isset($this->map[$class])) {
return $this->map[$class];
}
}
}

View File

@ -0,0 +1,265 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* UniversalClassLoader implements a "universal" autoloader for PHP 5.3.
*
* It is able to load classes that use either:
*
* * The technical interoperability standards for PHP 5.3 namespaces and
* class names (http://groups.google.com/group/php-standards/web/psr-0-final-proposal);
*
* * The PEAR naming convention for classes (http://pear.php.net/).
*
* Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be
* looked for in a list of locations to ease the vendoring of a sub-set of
* classes for large projects.
*
* Example usage:
*
* $loader = new UniversalClassLoader();
*
* // register classes with namespaces
* $loader->registerNamespaces(array(
* 'Symfony\Component' => __DIR__.'/component',
* 'Symfony' => __DIR__.'/framework',
* 'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'),
* ));
*
* // register a library using the PEAR naming convention
* $loader->registerPrefixes(array(
* 'Swift_' => __DIR__.'/Swift',
* ));
*
* // activate the autoloader
* $loader->register();
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class UniversalClassLoader
{
private $namespaces = array();
private $prefixes = array();
private $namespaceFallbacks = array();
private $prefixFallbacks = array();
/**
* Gets the configured namespaces.
*
* @return array A hash with namespaces as keys and directories as values
*/
public function getNamespaces()
{
return $this->namespaces;
}
/**
* Gets the configured class prefixes.
*
* @return array A hash with class prefixes as keys and directories as values
*/
public function getPrefixes()
{
return $this->prefixes;
}
/**
* Gets the directory(ies) to use as a fallback for namespaces.
*
* @return array An array of directories
*/
public function getNamespaceFallbacks()
{
return $this->namespaceFallbacks;
}
/**
* Gets the directory(ies) to use as a fallback for class prefixes.
*
* @return array An array of directories
*/
public function getPrefixFallbacks()
{
return $this->prefixFallbacks;
}
/**
* Registers the directory to use as a fallback for namespaces.
*
* @param array $dirs An array of directories
*
* @api
*/
public function registerNamespaceFallbacks(array $dirs)
{
$this->namespaceFallbacks = $dirs;
}
/**
* Registers the directory to use as a fallback for class prefixes.
*
* @param array $dirs An array of directories
*
* @api
*/
public function registerPrefixFallbacks(array $dirs)
{
$this->prefixFallbacks = $dirs;
}
/**
* Registers an array of namespaces
*
* @param array $namespaces An array of namespaces (namespaces as keys and locations as values)
*
* @api
*/
public function registerNamespaces(array $namespaces)
{
foreach ($namespaces as $namespace => $locations) {
$this->namespaces[$namespace] = (array) $locations;
}
}
/**
* Registers a namespace.
*
* @param string $namespace The namespace
* @param array|string $paths The location(s) of the namespace
*
* @api
*/
public function registerNamespace($namespace, $paths)
{
$this->namespaces[$namespace] = (array) $paths;
}
/**
* Registers an array of classes using the PEAR naming convention.
*
* @param array $classes An array of classes (prefixes as keys and locations as values)
*
* @api
*/
public function registerPrefixes(array $classes)
{
foreach ($classes as $prefix => $locations) {
$this->prefixes[$prefix] = (array) $locations;
}
}
/**
* Registers a set of classes using the PEAR naming convention.
*
* @param string $prefix The classes prefix
* @param array|string $paths The location(s) of the classes
*
* @api
*/
public function registerPrefix($prefix, $paths)
{
$this->prefixes[$prefix] = (array) $paths;
}
/**
* Registers this instance as an autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not
*
* @api
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
require $file;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|null The path, if found
*/
public function findFile($class)
{
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$namespace = substr($class, 0, $pos);
foreach ($this->namespaces as $ns => $dirs) {
if (0 !== strpos($namespace, $ns)) {
continue;
}
foreach ($dirs as $dir) {
$className = substr($class, $pos + 1);
$file = $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $className).'.php';
if (file_exists($file)) {
return $file;
}
}
}
foreach ($this->namespaceFallbacks as $dir) {
$file = $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php';
if (file_exists($file)) {
return $file;
}
}
} else {
// PEAR-like class name
foreach ($this->prefixes as $prefix => $dirs) {
if (0 !== strpos($class, $prefix)) {
continue;
}
foreach ($dirs as $dir) {
$file = $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php';
if (file_exists($file)) {
return $file;
}
}
}
foreach ($this->prefixFallbacks as $dir) {
$file = $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php';
if (file_exists($file)) {
return $file;
}
}
}
}
}

View File

@ -0,0 +1,22 @@
{
"name": "symfony/class-loader",
"type": "library",
"description": "Symfony ClassLoader Component",
"keywords": [],
"homepage": "http://symfony.com",
"version": "2.0.4",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.2"
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* Request represents an HTTP request from an Apache server.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ApacheRequest extends Request
{
/**
* {@inheritdoc}
*/
protected function prepareRequestUri()
{
return $this->server->get('REQUEST_URI');
}
/**
* {@inheritdoc}
*/
protected function prepareBaseUrl()
{
$baseUrl = $this->server->get('SCRIPT_NAME');
if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) {
// assume mod_rewrite
return rtrim(dirname($baseUrl), '/\\');
}
return $baseUrl;
}
/**
* {@inheritdoc}
*/
protected function preparePathInfo()
{
return $this->server->get('PATH_INFO');
}
}

View File

@ -0,0 +1,207 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* Represents a cookie
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @api
*/
class Cookie
{
protected $name;
protected $value;
protected $domain;
protected $expire;
protected $path;
protected $secure;
protected $httpOnly;
/**
* Constructor.
*
* @param string $name The name of the cookie
* @param string $value The value of the cookie
* @param integer|string|\DateTime $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string $domain The domain that the cookie is available to
* @param Boolean $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
* @param Boolean $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
*
* @api
*/
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true)
{
// from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
}
if (preg_match("/[,; \t\r\n\013\014]/", $value)) {
throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
}
if (empty($name)) {
throw new \InvalidArgumentException('The cookie name cannot be empty.');
}
// convert expiration time to a Unix timestamp
if ($expire instanceof \DateTime) {
$expire = $expire->format('U');
} elseif (!is_numeric($expire)) {
$expire = strtotime($expire);
if (false === $expire || -1 === $expire) {
throw new \InvalidArgumentException('The cookie expiration time is not valid.');
}
}
$this->name = $name;
$this->value = $value;
$this->domain = $domain;
$this->expire = $expire;
$this->path = empty($path) ? '/' : $path;
$this->secure = (Boolean) $secure;
$this->httpOnly = (Boolean) $httpOnly;
}
public function __toString()
{
$str = urlencode($this->getName()).'=';
if ('' === (string) $this->getValue()) {
$str .= 'deleted; expires='.gmdate("D, d-M-Y H:i:s T", time() - 31536001);
} else {
$str .= urlencode($this->getValue());
if ($this->getExpiresTime() !== 0) {
$str .= '; expires='.gmdate("D, d-M-Y H:i:s T", $this->getExpiresTime());
}
}
if ('/' !== $this->path) {
$str .= '; path='.$this->path;
}
if (null !== $this->getDomain()) {
$str .= '; domain='.$this->getDomain();
}
if (true === $this->isSecure()) {
$str .= '; secure';
}
if (true === $this->isHttpOnly()) {
$str .= '; httponly';
}
return $str;
}
/**
* Gets the name of the cookie.
*
* @return string
*
* @api
*/
public function getName()
{
return $this->name;
}
/**
* Gets the value of the cookie.
*
* @return string
*
* @api
*/
public function getValue()
{
return $this->value;
}
/**
* Gets the domain that the cookie is available to.
*
* @return string
*
* @api
*/
public function getDomain()
{
return $this->domain;
}
/**
* Gets the time the cookie expires.
*
* @return integer
*
* @api
*/
public function getExpiresTime()
{
return $this->expire;
}
/**
* Gets the path on the server in which the cookie will be available on.
*
* @return string
*
* @api
*/
public function getPath()
{
return $this->path;
}
/**
* Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
*
* @return Boolean
*
* @api
*/
public function isSecure()
{
return $this->secure;
}
/**
* Checks whether the cookie will be made accessible only through the HTTP protocol.
*
* @return Boolean
*
* @api
*/
public function isHttpOnly()
{
return $this->httpOnly;
}
/**
* Whether this cookie is about to be cleared
*
* @return Boolean
*
* @api
*/
public function isCleared()
{
return $this->expire < time();
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\Exception;
/**
* Thrown when the access on a file was denied.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class AccessDeniedException extends FileException
{
/**
* Constructor.
*
* @param string $path The path to the accessed file
*/
public function __construct($path)
{
parent::__construct(sprintf('The file %s could not be accessed', $path));
}
}

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\Exception;
/**
* Thrown when an error occurred in the component File
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FileException extends \RuntimeException
{
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\Exception;
/**
* Thrown when a file was not found
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FileNotFoundException extends FileException
{
/**
* Constructor.
*
* @param string $path The path to the file that was not found
*/
public function __construct($path)
{
parent::__construct(sprintf('The file "%s" does not exist', $path));
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\Exception;
class UnexpectedTypeException extends FileException
{
public function __construct($value, $expectedType)
{
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\Exception;
/**
* Thrown when an error occurred during file upload
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class UploadException extends FileException
{
}

View File

@ -0,0 +1,544 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
/**
* A file in the file system.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*
* @api
*/
class File extends \SplFileInfo
{
/**
* A map of mime types and their default extensions.
*
* @var array
*/
static protected $defaultExtensions = array(
'application/andrew-inset' => 'ez',
'application/appledouble' => 'base64',
'application/applefile' => 'base64',
'application/commonground' => 'dp',
'application/cprplayer' => 'pqi',
'application/dsptype' => 'tsp',
'application/excel' => 'xls',
'application/font-tdpfr' => 'pfr',
'application/futuresplash' => 'spl',
'application/hstu' => 'stk',
'application/hyperstudio' => 'stk',
'application/javascript' => 'js',
'application/mac-binhex40' => 'hqx',
'application/mac-compactpro' => 'cpt',
'application/mbed' => 'mbd',
'application/mirage' => 'mfp',
'application/msword' => 'doc',
'application/ocsp-request' => 'orq',
'application/ocsp-response' => 'ors',
'application/octet-stream' => 'bin',
'application/oda' => 'oda',
'application/ogg' => 'ogg',
'application/pdf' => 'pdf',
'application/x-pdf' => 'pdf',
'application/pgp-encrypted' => '7bit',
'application/pgp-keys' => '7bit',
'application/pgp-signature' => 'sig',
'application/pkcs10' => 'p10',
'application/pkcs7-mime' => 'p7m',
'application/pkcs7-signature' => 'p7s',
'application/pkix-cert' => 'cer',
'application/pkix-crl' => 'crl',
'application/pkix-pkipath' => 'pkipath',
'application/pkixcmp' => 'pki',
'application/postscript' => 'ps',
'application/presentations' => 'shw',
'application/prs.cww' => 'cw',
'application/prs.nprend' => 'rnd',
'application/quest' => 'qrt',
'application/rtf' => 'rtf',
'application/sgml-open-catalog' => 'soc',
'application/sieve' => 'siv',
'application/smil' => 'smi',
'application/toolbook' => 'tbk',
'application/vnd.3gpp.pic-bw-large' => 'plb',
'application/vnd.3gpp.pic-bw-small' => 'psb',
'application/vnd.3gpp.pic-bw-var' => 'pvb',
'application/vnd.3gpp.sms' => 'sms',
'application/vnd.acucorp' => 'atc',
'application/vnd.adobe.xfdf' => 'xfdf',
'application/vnd.amiga.amu' => 'ami',
'application/vnd.blueice.multipass' => 'mpm',
'application/vnd.cinderella' => 'cdy',
'application/vnd.cosmocaller' => 'cmc',
'application/vnd.criticaltools.wbs+xml' => 'wbs',
'application/vnd.curl' => 'curl',
'application/vnd.data-vision.rdz' => 'rdz',
'application/vnd.dreamfactory' => 'dfac',
'application/vnd.fsc.weblaunch' => 'fsc',
'application/vnd.genomatix.tuxedo' => 'txd',
'application/vnd.hbci' => 'hbci',
'application/vnd.hhe.lesson-player' => 'les',
'application/vnd.hp-hpgl' => 'plt',
'application/vnd.ibm.electronic-media' => 'emm',
'application/vnd.ibm.rights-management' => 'irm',
'application/vnd.ibm.secure-container' => 'sc',
'application/vnd.ipunplugged.rcprofile' => 'rcprofile',
'application/vnd.irepository.package+xml' => 'irp',
'application/vnd.jisp' => 'jisp',
'application/vnd.kde.karbon' => 'karbon',
'application/vnd.kde.kchart' => 'chrt',
'application/vnd.kde.kformula' => 'kfo',
'application/vnd.kde.kivio' => 'flw',
'application/vnd.kde.kontour' => 'kon',
'application/vnd.kde.kpresenter' => 'kpr',
'application/vnd.kde.kspread' => 'ksp',
'application/vnd.kde.kword' => 'kwd',
'application/vnd.kenameapp' => 'htke',
'application/vnd.kidspiration' => 'kia',
'application/vnd.kinar' => 'kne',
'application/vnd.llamagraphics.life-balance.desktop' => 'lbd',
'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe',
'application/vnd.lotus-1-2-3' => 'wks',
'application/vnd.mcd' => 'mcd',
'application/vnd.mfmp' => 'mfm',
'application/vnd.micrografx.flo' => 'flo',
'application/vnd.micrografx.igx' => 'igx',
'application/vnd.mif' => 'mif',
'application/vnd.mophun.application' => 'mpn',
'application/vnd.mophun.certificate' => 'mpc',
'application/vnd.mozilla.xul+xml' => 'xul',
'application/vnd.ms-artgalry' => 'cil',
'application/vnd.ms-asf' => 'asf',
'application/vnd.ms-excel' => 'xls',
'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm',
'application/vnd.ms-lrm' => 'lrm',
'application/vnd.ms-powerpoint' => 'ppt',
'application/vnd.ms-project' => 'mpp',
'application/vnd.ms-tnef' => 'base64',
'application/vnd.ms-works' => 'base64',
'application/vnd.ms-wpl' => 'wpl',
'application/vnd.mseq' => 'mseq',
'application/vnd.nervana' => 'ent',
'application/vnd.nokia.radio-preset' => 'rpst',
'application/vnd.nokia.radio-presets' => 'rpss',
'application/vnd.oasis.opendocument.text' => 'odt',
'application/vnd.oasis.opendocument.text-template' => 'ott',
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/vnd.oasis.opendocument.text-master' => 'odm',
'application/vnd.oasis.opendocument.graphics' => 'odg',
'application/vnd.oasis.opendocument.graphics-template' => 'otg',
'application/vnd.oasis.opendocument.presentation' => 'odp',
'application/vnd.oasis.opendocument.presentation-template' => 'otp',
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots',
'application/vnd.oasis.opendocument.chart' => 'odc',
'application/vnd.oasis.opendocument.formula' => 'odf',
'application/vnd.oasis.opendocument.database' => 'odb',
'application/vnd.oasis.opendocument.image' => 'odi',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.palm' => 'prc',
'application/vnd.picsel' => 'efif',
'application/vnd.pvi.ptid1' => 'pti',
'application/vnd.quark.quarkxpress' => 'qxd',
'application/vnd.sealed.doc' => 'sdoc',
'application/vnd.sealed.eml' => 'seml',
'application/vnd.sealed.mht' => 'smht',
'application/vnd.sealed.ppt' => 'sppt',
'application/vnd.sealed.xls' => 'sxls',
'application/vnd.sealedmedia.softseal.html' => 'stml',
'application/vnd.sealedmedia.softseal.pdf' => 'spdf',
'application/vnd.seemail' => 'see',
'application/vnd.smaf' => 'mmf',
'application/vnd.sun.xml.calc' => 'sxc',
'application/vnd.sun.xml.calc.template' => 'stc',
'application/vnd.sun.xml.draw' => 'sxd',
'application/vnd.sun.xml.draw.template' => 'std',
'application/vnd.sun.xml.impress' => 'sxi',
'application/vnd.sun.xml.impress.template' => 'sti',
'application/vnd.sun.xml.math' => 'sxm',
'application/vnd.sun.xml.writer' => 'sxw',
'application/vnd.sun.xml.writer.global' => 'sxg',
'application/vnd.sun.xml.writer.template' => 'stw',
'application/vnd.sus-calendar' => 'sus',
'application/vnd.vidsoft.vidconference' => 'vsc',
'application/vnd.visio' => 'vsd',
'application/vnd.visionary' => 'vis',
'application/vnd.wap.sic' => 'sic',
'application/vnd.wap.slc' => 'slc',
'application/vnd.wap.wbxml' => 'wbxml',
'application/vnd.wap.wmlc' => 'wmlc',
'application/vnd.wap.wmlscriptc' => 'wmlsc',
'application/vnd.webturbo' => 'wtb',
'application/vnd.wordperfect' => 'wpd',
'application/vnd.wqd' => 'wqd',
'application/vnd.wv.csp+wbxml' => 'wv',
'application/vnd.wv.csp+xml' => '8bit',
'application/vnd.wv.ssp+xml' => '8bit',
'application/vnd.yamaha.hv-dic' => 'hvd',
'application/vnd.yamaha.hv-script' => 'hvs',
'application/vnd.yamaha.hv-voice' => 'hvp',
'application/vnd.yamaha.smaf-audio' => 'saf',
'application/vnd.yamaha.smaf-phrase' => 'spf',
'application/vocaltec-media-desc' => 'vmd',
'application/vocaltec-media-file' => 'vmf',
'application/vocaltec-talker' => 'vtk',
'application/watcherinfo+xml' => 'wif',
'application/wordperfect5.1' => 'wp5',
'application/x-123' => 'wk',
'application/x-7th_level_event' => '7ls',
'application/x-authorware-bin' => 'aab',
'application/x-authorware-map' => 'aam',
'application/x-authorware-seg' => 'aas',
'application/x-bcpio' => 'bcpio',
'application/x-bleeper' => 'bleep',
'application/x-bzip2' => 'bz2',
'application/x-cdlink' => 'vcd',
'application/x-chat' => 'chat',
'application/x-chess-pgn' => 'pgn',
'application/x-compress' => 'z',
'application/x-cpio' => 'cpio',
'application/x-cprplayer' => 'pqf',
'application/x-csh' => 'csh',
'application/x-cu-seeme' => 'csm',
'application/x-cult3d-object' => 'co',
'application/x-debian-package' => 'deb',
'application/x-director' => 'dcr',
'application/x-dvi' => 'dvi',
'application/x-envoy' => 'evy',
'application/x-futuresplash' => 'spl',
'application/x-gtar' => 'gtar',
'application/x-gzip' => 'gz',
'application/x-hdf' => 'hdf',
'application/x-hep' => 'hep',
'application/x-html+ruby' => 'rhtml',
'application/x-httpd-miva' => 'mv',
'application/x-httpd-php' => 'phtml',
'application/x-ica' => 'ica',
'application/x-imagemap' => 'imagemap',
'application/x-ipix' => 'ipx',
'application/x-ipscript' => 'ips',
'application/x-java-archive' => 'jar',
'application/x-java-jnlp-file' => 'jnlp',
'application/x-java-serialized-object' => 'ser',
'application/x-java-vm' => 'class',
'application/x-javascript' => 'js',
'application/x-koan' => 'skp',
'application/x-latex' => 'latex',
'application/x-mac-compactpro' => 'cpt',
'application/x-maker' => 'frm',
'application/x-mathcad' => 'mcd',
'application/x-midi' => 'mid',
'application/x-mif' => 'mif',
'application/x-msaccess' => 'mda',
'application/x-msdos-program' => 'com',
'application/x-msdownload' => 'base64',
'application/x-msexcel' => 'xls',
'application/x-msword' => 'doc',
'application/x-netcdf' => 'nc',
'application/x-ns-proxy-autoconfig' => 'pac',
'application/x-pagemaker' => 'pm5',
'application/x-perl' => 'pl',
'application/x-pn-realmedia' => 'rp',
'application/x-python' => 'py',
'application/x-quicktimeplayer' => 'qtl',
'application/x-rar-compressed' => 'rar',
'application/x-ruby' => 'rb',
'application/x-sh' => 'sh',
'application/x-shar' => 'shar',
'application/x-shockwave-flash' => 'swf',
'application/x-sprite' => 'spr',
'application/x-spss' => 'sav',
'application/x-spt' => 'spt',
'application/x-stuffit' => 'sit',
'application/x-sv4cpio' => 'sv4cpio',
'application/x-sv4crc' => 'sv4crc',
'application/x-tar' => 'tar',
'application/x-tcl' => 'tcl',
'application/x-tex' => 'tex',
'application/x-texinfo' => 'texinfo',
'application/x-troff' => 't',
'application/x-troff-man' => 'man',
'application/x-troff-me' => 'me',
'application/x-troff-ms' => 'ms',
'application/x-twinvq' => 'vqf',
'application/x-twinvq-plugin' => 'vqe',
'application/x-ustar' => 'ustar',
'application/x-vmsbackup' => 'bck',
'application/x-wais-source' => 'src',
'application/x-wingz' => 'wz',
'application/x-word' => 'base64',
'application/x-wordperfect6.1' => 'wp6',
'application/x-x509-ca-cert' => 'crt',
'application/x-zip-compressed' => 'zip',
'application/xhtml+xml' => 'xhtml',
'application/zip' => 'zip',
'audio/3gpp' => '3gpp',
'audio/amr' => 'amr',
'audio/amr-wb' => 'awb',
'audio/basic' => 'au',
'audio/evrc' => 'evc',
'audio/l16' => 'l16',
'audio/midi' => 'mid',
'audio/mpeg' => 'mp3',
'audio/prs.sid' => 'sid',
'audio/qcelp' => 'qcp',
'audio/smv' => 'smv',
'audio/vnd.audiokoz' => 'koz',
'audio/vnd.digital-winds' => 'eol',
'audio/vnd.everad.plj' => 'plj',
'audio/vnd.lucent.voice' => 'lvp',
'audio/vnd.nokia.mobile-xmf' => 'mxmf',
'audio/vnd.nortel.vbk' => 'vbk',
'audio/vnd.nuera.ecelp4800' => 'ecelp4800',
'audio/vnd.nuera.ecelp7470' => 'ecelp7470',
'audio/vnd.nuera.ecelp9600' => 'ecelp9600',
'audio/vnd.sealedmedia.softseal.mpeg' => 'smp3',
'audio/voxware' => 'vox',
'audio/x-aiff' => 'aif',
'audio/x-mid' => 'mid',
'audio/x-midi' => 'mid',
'audio/x-mpeg' => 'mp2',
'audio/x-mpegurl' => 'mpu',
'audio/x-pn-realaudio' => 'rm',
'audio/x-pn-realaudio-plugin' => 'rpm',
'audio/x-realaudio' => 'ra',
'audio/x-wav' => 'wav',
'chemical/x-csml' => 'csm',
'chemical/x-embl-dl-nucleotide' => 'emb',
'chemical/x-gaussian-cube' => 'cube',
'chemical/x-gaussian-input' => 'gau',
'chemical/x-jcamp-dx' => 'jdx',
'chemical/x-mdl-molfile' => 'mol',
'chemical/x-mdl-rxnfile' => 'rxn',
'chemical/x-mdl-tgf' => 'tgf',
'chemical/x-mopac-input' => 'mop',
'chemical/x-pdb' => 'pdb',
'chemical/x-rasmol' => 'scr',
'chemical/x-xyz' => 'xyz',
'drawing/dwf' => 'dwf',
'drawing/x-dwf' => 'dwf',
'i-world/i-vrml' => 'ivr',
'image/bmp' => 'bmp',
'image/cewavelet' => 'wif',
'image/cis-cod' => 'cod',
'image/fif' => 'fif',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jp2' => 'jp2',
'image/jpeg' => 'jpg',
'image/jpm' => 'jpm',
'image/jpx' => 'jpf',
'image/pict' => 'pic',
'image/pjpeg' => 'jpg',
'image/png' => 'png',
'image/targa' => 'tga',
'image/tiff' => 'tif',
'image/vn-svf' => 'svf',
'image/vnd.dgn' => 'dgn',
'image/vnd.djvu' => 'djvu',
'image/vnd.dwg' => 'dwg',
'image/vnd.glocalgraphics.pgb' => 'pgb',
'image/vnd.microsoft.icon' => 'ico',
'image/vnd.ms-modi' => 'mdi',
'image/vnd.sealed.png' => 'spng',
'image/vnd.sealedmedia.softseal.gif' => 'sgif',
'image/vnd.sealedmedia.softseal.jpg' => 'sjpg',
'image/vnd.wap.wbmp' => 'wbmp',
'image/x-bmp' => 'bmp',
'image/x-cmu-raster' => 'ras',
'image/x-freehand' => 'fh4',
'image/x-png' => 'png',
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'message/external-body' => '8bit',
'message/news' => '8bit',
'message/partial' => '8bit',
'message/rfc822' => '8bit',
'model/iges' => 'igs',
'model/mesh' => 'msh',
'model/vnd.parasolid.transmit.binary' => 'x_b',
'model/vnd.parasolid.transmit.text' => 'x_t',
'model/vrml' => 'wrl',
'multipart/alternative' => '8bit',
'multipart/appledouble' => '8bit',
'multipart/digest' => '8bit',
'multipart/mixed' => '8bit',
'multipart/parallel' => '8bit',
'text/comma-separated-values' => 'csv',
'text/css' => 'css',
'text/html' => 'html',
'text/plain' => 'txt',
'text/prs.fallenstein.rst' => 'rst',
'text/richtext' => 'rtx',
'text/rtf' => 'rtf',
'text/sgml' => 'sgml',
'text/tab-separated-values' => 'tsv',
'text/vnd.net2phone.commcenter.command' => 'ccc',
'text/vnd.sun.j2me.app-descriptor' => 'jad',
'text/vnd.wap.si' => 'si',
'text/vnd.wap.sl' => 'sl',
'text/vnd.wap.wml' => 'wml',
'text/vnd.wap.wmlscript' => 'wmls',
'text/x-hdml' => 'hdml',
'text/x-setext' => 'etx',
'text/x-sgml' => 'sgml',
'text/x-speech' => 'talk',
'text/x-vcalendar' => 'vcs',
'text/x-vcard' => 'vcf',
'text/xml' => 'xml',
'ulead/vrml' => 'uvr',
'video/3gpp' => '3gp',
'video/dl' => 'dl',
'video/gl' => 'gl',
'video/mj2' => 'mj2',
'video/mpeg' => 'mpeg',
'video/quicktime' => 'mov',
'video/vdo' => 'vdo',
'video/vivo' => 'viv',
'video/vnd.fvt' => 'fvt',
'video/vnd.mpegurl' => 'mxu',
'video/vnd.nokia.interleaved-multimedia' => 'nim',
'video/vnd.objectvideo' => 'mp4',
'video/vnd.sealed.mpeg1' => 's11',
'video/vnd.sealed.mpeg4' => 'smpg',
'video/vnd.sealed.swf' => 'sswf',
'video/vnd.sealedmedia.softseal.mov' => 'smov',
'video/vnd.vivo' => 'vivo',
'video/x-fli' => 'fli',
'video/x-ms-asf' => 'asf',
'video/x-ms-wmv' => 'wmv',
'video/x-msvideo' => 'avi',
'video/x-sgi-movie' => 'movie',
'x-chemical/x-pdb' => 'pdb',
'x-chemical/x-xyz' => 'xyz',
'x-conference/x-cooltalk' => 'ice',
'x-drawing/dwf' => 'dwf',
'x-world/x-d96' => 'd',
'x-world/x-svr' => 'svr',
'x-world/x-vream' => 'vrw',
'x-world/x-vrml' => 'wrl',
);
/**
* Constructs a new file from the given path.
*
* @param string $path The path to the file
*
* @throws FileNotFoundException If the given path is not a file
*
* @api
*/
public function __construct($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
parent::__construct($path);
}
/**
* Returns the extension based on the mime type.
*
* If the mime type is unknown, returns null.
*
* @return string|null The guessed extension or null if it cannot be guessed
*
* @api
*/
public function guessExtension()
{
$type = $this->getMimeType();
return isset(static::$defaultExtensions[$type]) ? static::$defaultExtensions[$type] : null;
}
/**
* Returns the mime type of the file.
*
* The mime type is guessed using the functions finfo(), mime_content_type()
* and the system binary "file" (in this order), depending on which of those
* is available on the current operating system.
*
* @return string|null The guessed mime type (i.e. "application/pdf")
*
* @api
*/
public function getMimeType()
{
$guesser = MimeTypeGuesser::getInstance();
return $guesser->guess($this->getPathname());
}
/**
* Returns the extension of the file.
*
* \SplFileInfo::getExtension() is not available before PHP 5.3.6
*
* @return string The extension
*
* @api
*/
public function getExtension()
{
return pathinfo($this->getBasename(), PATHINFO_EXTENSION);
}
/**
* Moves the file to a new location.
*
* @param string $directory The destination folder
* @param string $name The new file name
*
* @return File A File object representing the new file
*
* @throws FileException if the target file could not be created
*
* @api
*/
public function move($directory, $name = null)
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true)) {
throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
}
} elseif (!is_writable($directory)) {
throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
}
$target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : basename($name));
if (!@rename($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
}
chmod($target, 0666);
return new File($target);
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type using the PHP function mime_content_type().
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class ContentTypeMimeTypeGuesser implements MimeTypeGuesserInterface
{
/**
* Returns whether this guesser is supported on the current OS/PHP setup
*
* @return Boolean
*/
static public function isSupported()
{
return function_exists('mime_content_type');
}
/**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
$type = mime_content_type($path);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';')) {
$type = substr($type, 0, $pos);
}
return $type;
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type with the binary "file" (only available on *nix)
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
{
/**
* Returns whether this guesser is supported on the current OS
*
* @return Boolean
*/
static public function isSupported()
{
return !strstr(PHP_OS, 'WIN');
}
/**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($path)), $return);
if ($return > 0) {
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match)) {
// it's not a type, but an error message
return null;
}
return $match[1];
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Guesses the mime type using the PECL extension FileInfo
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
{
/**
* Returns whether this guesser is supported on the current OS/PHP setup
*
* @return Boolean
*/
static public function isSupported()
{
return function_exists('finfo_open');
}
/**
* Guesses the mime type of the file with the given path
*
* @see MimeTypeGuesserInterface::guess()
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
if (!self::isSupported()) {
return null;
}
if (!$finfo = new \finfo(FILEINFO_MIME_TYPE)) {
return null;
}
return $finfo->file($path);
}
}

View File

@ -0,0 +1,125 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\MimeType;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* A singleton mime type guesser.
*
* By default, all mime type guessers provided by the framework are installed
* (if available on the current OS/PHP setup). You can register custom
* guessers by calling the register() method on the singleton instance.
*
* <code>
* $guesser = MimeTypeGuesser::getInstance();
* $guesser->register(new MyCustomMimeTypeGuesser());
* </code>
*
* The last registered guesser is preferred over previously registered ones.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class MimeTypeGuesser implements MimeTypeGuesserInterface
{
/**
* The singleton instance
* @var MimeTypeGuesser
*/
static private $instance = null;
/**
* All registered MimeTypeGuesserInterface instances
* @var array
*/
protected $guessers = array();
/**
* Returns the singleton instance
*
* @return MimeTypeGuesser
*/
static public function getInstance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Registers all natively provided mime type guessers
*/
private function __construct()
{
if (FileBinaryMimeTypeGuesser::isSupported()) {
$this->register(new FileBinaryMimeTypeGuesser());
}
if (ContentTypeMimeTypeGuesser::isSupported()) {
$this->register(new ContentTypeMimeTypeGuesser());
}
if (FileinfoMimeTypeGuesser::isSupported()) {
$this->register(new FileinfoMimeTypeGuesser());
}
}
/**
* Registers a new mime type guesser
*
* When guessing, this guesser is preferred over previously registered ones.
*
* @param MimeTypeGuesserInterface $guesser
*/
public function register(MimeTypeGuesserInterface $guesser)
{
array_unshift($this->guessers, $guesser);
}
/**
* Tries to guess the mime type of the given file
*
* The file is passed to each registered mime type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not NULL, this method terminates and returns the
* value.
*
* @param string $path The path to the file
* @return string The mime type or NULL, if none could be guessed
* @throws FileException If the file does not exist
*/
public function guess($path)
{
if (!is_file($path)) {
throw new FileNotFoundException($path);
}
if (!is_readable($path)) {
throw new AccessDeniedException($path);
}
$mimeType = null;
foreach ($this->guessers as $guesser) {
$mimeType = $guesser->guess($path);
if (null !== $mimeType) {
break;
}
}
return $mimeType;
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File\MimeType;
/**
* Guesses the mime type of a file
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
interface MimeTypeGuesserInterface
{
/**
* Guesses the mime type of the file with the given path
*
* @param string $path The path to the file
* @return string The mime type or NULL, if none could be guessed
* @throws FileNotFoundException If the file does not exist
* @throws AccessDeniedException If the file could not be read
*/
function guess($path);
}

View File

@ -0,0 +1,225 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\File;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
/**
* A file uploaded through a form.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class UploadedFile extends File
{
/**
* Whether the test mode is activated.
*
* Local files are used in test mode hence the code should not enforce HTTP uploads.
*
* @var Boolean
*/
private $test = false;
/**
* The original name of the uploaded file.
*
* @var string
*/
private $originalName;
/**
* The mime type provided by the uploader.
*
* @var string
*/
private $mimeType;
/**
* The file size provided by the uploader.
*
* @var string
*/
private $size;
/**
* The UPLOAD_ERR_XXX constant provided by the uploader.
*
* @var integer
*/
private $error;
/**
* Accepts the information of the uploaded file as provided by the PHP global $_FILES.
*
* The file object is only created when the uploaded file is valid (i.e. when the
* isValid() method returns true). Otherwise the only methods that could be called
* on an UploadedFile instance are:
*
* * getClientOriginalName,
* * getClientMimeType,
* * isValid,
* * getError.
*
* Calling any other method on an non-valid instance will cause an unpredictable result.
*
* @param string $path The full temporary path to the file
* @param string $originalName The original file name
* @param string $mimeType The type of the file as provided by PHP
* @param integer $size The file size
* @param integer $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants)
* @param Boolean $test Whether the test mode is active
*
* @throws FileException If file_uploads is disabled
* @throws FileNotFoundException If the file does not exist
*
* @api
*/
public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
{
if (!ini_get('file_uploads')) {
throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
}
$this->originalName = basename($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream';
$this->size = $size;
$this->error = $error ?: UPLOAD_ERR_OK;
$this->test = (Boolean) $test;
if (UPLOAD_ERR_OK === $this->error) {
parent::__construct($path);
}
}
/**
* Returns the original file name.
*
* It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value.
*
* @return string|null The original name
*
* @api
*/
public function getClientOriginalName()
{
return $this->originalName;
}
/**
* Returns the file mime type.
*
* It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value.
*
* @return string|null The mime type
*
* @api
*/
public function getClientMimeType()
{
return $this->mimeType;
}
/**
* Returns the file size.
*
* It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value.
*
* @return integer|null The file size
*
* @api
*/
public function getClientSize()
{
return $this->size;
}
/**
* Returns the upload error.
*
* If the upload was successful, the constant UPLOAD_ERR_OK is returned.
* Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
*
* @return integer The upload error
*
* @api
*/
public function getError()
{
return $this->error;
}
/**
* Returns whether the file was uploaded successfully.
*
* @return Boolean True if no error occurred during uploading
*
* @api
*/
public function isValid()
{
return $this->error === UPLOAD_ERR_OK;
}
/**
* Moves the file to a new location.
*
* @param string $directory The destination folder
* @param string $name The new file name
*
* @return File A File object representing the new file
*
* @throws FileException if the file has not been uploaded via Http
*
* @api
*/
public function move($directory, $name = null)
{
if ($this->isValid() && ($this->test || is_uploaded_file($this->getPathname()))) {
return parent::move($directory, $name);
}
throw new FileException(sprintf('The file "%s" has not been uploaded via Http', $this->getPathname()));
}
/**
* Returns the maximum size of an uploaded file as configured in php.ini
*
* @return type The maximum size of an uploaded file in bytes
*/
static public function getMaxFilesize()
{
$max = trim(ini_get('upload_max_filesize'));
if ('' === $max) {
return PHP_INT_MAX;
}
switch (strtolower(substr($max, -1))) {
case 'g':
$max *= 1024;
case 'm':
$max *= 1024;
case 'k':
$max *= 1024;
}
return (integer) $max;
}
}

View File

@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* FileBag is a container for HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
*
* @api
*/
class FileBag extends ParameterBag
{
static private $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
/**
* Constructor.
*
* @param array $parameters An array of HTTP files
*
* @api
*/
public function __construct(array $parameters = array())
{
$this->replace($parameters);
}
/**
* (non-PHPdoc)
* @see Symfony\Component\HttpFoundation\ParameterBag::replace()
*
* @api
*/
public function replace(array $files = array())
{
$this->parameters = array();
$this->add($files);
}
/**
* (non-PHPdoc)
* @see Symfony\Component\HttpFoundation\ParameterBag::set()
*
* @api
*/
public function set($key, $value)
{
if (is_array($value) || $value instanceof UploadedFile) {
parent::set($key, $this->convertFileInformation($value));
} else {
throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.');
}
}
/**
* (non-PHPdoc)
* @see Symfony\Component\HttpFoundation\ParameterBag::add()
*
* @api
*/
public function add(array $files = array())
{
foreach ($files as $key => $file) {
$this->set($key, $file);
}
}
/**
* Converts uploaded files to UploadedFile instances.
*
* @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information
*
* @return array A (multi-dimensional) array of UploadedFile instances
*/
protected function convertFileInformation($file)
{
if ($file instanceof UploadedFile) {
return $file;
}
$file = $this->fixPhpFilesArray($file);
if (is_array($file)) {
$keys = array_keys($file);
sort($keys);
if ($keys == self::$fileKeys) {
if (UPLOAD_ERR_NO_FILE == $file['error']) {
$file = null;
} else {
$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']);
}
} else {
$file = array_map(array($this, 'convertFileInformation'), $file);
}
}
return $file;
}
/**
* Fixes a malformed PHP $_FILES array.
*
* PHP has a bug that the format of the $_FILES array differs, depending on
* whether the uploaded file fields had normal field names or array-like
* field names ("normal" vs. "parent[child]").
*
* This method fixes the array to look like the "normal" $_FILES array.
*
* It's safe to pass an already converted array, in which case this method
* just returns the original array unmodified.
*
* @param array $data
* @return array
*/
protected function fixPhpFilesArray($data)
{
if (!is_array($data)) {
return $data;
}
$keys = array_keys($data);
sort($keys);
if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) {
return $data;
}
$files = $data;
foreach (self::$fileKeys as $k) {
unset($files[$k]);
}
foreach (array_keys($data['name']) as $key) {
$files[$key] = $this->fixPhpFilesArray(array(
'error' => $data['error'][$key],
'name' => $data['name'][$key],
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key]
));
}
return $files;
}
}

View File

@ -0,0 +1,306 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* HeaderBag is a container for HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class HeaderBag
{
protected $headers;
protected $cacheControl;
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
*
* @api
*/
public function __construct(array $headers = array())
{
$this->cacheControl = array();
$this->headers = array();
foreach ($headers as $key => $values) {
$this->set($key, $values);
}
}
/**
* Returns the headers as a string.
*
* @return string The headers
*/
public function __toString()
{
if (!$this->headers) {
return '';
}
$beautifier = function ($name) {
return preg_replace_callback('/\-(.)/', function ($match) { return '-'.strtoupper($match[1]); }, ucfirst($name));
};
$max = max(array_map('strlen', array_keys($this->headers))) + 1;
$content = '';
ksort($this->headers);
foreach ($this->headers as $name => $values) {
foreach ($values as $value) {
$content .= sprintf("%-{$max}s %s\r\n", $beautifier($name).':', $value);
}
}
return $content;
}
/**
* Returns the headers.
*
* @return array An array of headers
*
* @api
*/
public function all()
{
return $this->headers;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
*
* @api
*/
public function keys()
{
return array_keys($this->headers);
}
/**
* Replaces the current HTTP headers by a new set.
*
* @param array $headers An array of HTTP headers
*
* @api
*/
public function replace(array $headers = array())
{
$this->headers = array();
$this->add($headers);
}
/**
* Adds new headers the current HTTP headers set.
*
* @param array $headers An array of HTTP headers
*
* @api
*/
public function add(array $headers)
{
foreach ($headers as $key => $values) {
$this->set($key, $values);
}
}
/**
* Returns a header value by name.
*
* @param string $key The header name
* @param mixed $default The default value
* @param Boolean $first Whether to return the first value or all header values
*
* @return string|array The first header value if $first is true, an array of values otherwise
*
* @api
*/
public function get($key, $default = null, $first = true)
{
$key = strtr(strtolower($key), '_', '-');
if (!array_key_exists($key, $this->headers)) {
if (null === $default) {
return $first ? null : array();
}
return $first ? $default : array($default);
}
if ($first) {
return count($this->headers[$key]) ? $this->headers[$key][0] : $default;
}
return $this->headers[$key];
}
/**
* Sets a header by name.
*
* @param string $key The key
* @param string|array $values The value or an array of values
* @param Boolean $replace Whether to replace the actual value of not (true by default)
*
* @api
*/
public function set($key, $values, $replace = true)
{
$key = strtr(strtolower($key), '_', '-');
$values = (array) $values;
if (true === $replace || !isset($this->headers[$key])) {
$this->headers[$key] = $values;
} else {
$this->headers[$key] = array_merge($this->headers[$key], $values);
}
if ('cache-control' === $key) {
$this->cacheControl = $this->parseCacheControl($values[0]);
}
}
/**
* Returns true if the HTTP header is defined.
*
* @param string $key The HTTP header
*
* @return Boolean true if the parameter exists, false otherwise
*
* @api
*/
public function has($key)
{
return array_key_exists(strtr(strtolower($key), '_', '-'), $this->headers);
}
/**
* Returns true if the given HTTP header contains the given value.
*
* @param string $key The HTTP header name
* @param string $value The HTTP value
*
* @return Boolean true if the value is contained in the header, false otherwise
*
* @api
*/
public function contains($key, $value)
{
return in_array($value, $this->get($key, null, false));
}
/**
* Removes a header.
*
* @param string $key The HTTP header name
*
* @api
*/
public function remove($key)
{
$key = strtr(strtolower($key), '_', '-');
unset($this->headers[$key]);
if ('cache-control' === $key) {
$this->cacheControl = array();
}
}
/**
* Returns the HTTP header value converted to a date.
*
* @param string $key The parameter key
* @param \DateTime $default The default value
*
* @return \DateTime The filtered value
*
* @api
*/
public function getDate($key, \DateTime $default = null)
{
if (null === $value = $this->get($key)) {
return $default;
}
if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
}
return $date;
}
public function addCacheControlDirective($key, $value = true)
{
$this->cacheControl[$key] = $value;
$this->set('Cache-Control', $this->getCacheControlHeader());
}
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl);
}
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
}
public function removeCacheControlDirective($key)
{
unset($this->cacheControl[$key]);
$this->set('Cache-Control', $this->getCacheControlHeader());
}
protected function getCacheControlHeader()
{
$parts = array();
ksort($this->cacheControl);
foreach ($this->cacheControl as $key => $value) {
if (true === $value) {
$parts[] = $key;
} else {
if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
$value = '"'.$value.'"';
}
$parts[] = "$key=$value";
}
}
return implode(', ', $parts);
}
/**
* Parses a Cache-Control HTTP header.
*
* @param string $header The value of the Cache-Control HTTP header
*
* @return array An array representing the attribute values
*/
protected function parseCacheControl($header)
{
$cacheControl = array();
preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$cacheControl[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true);
}
return $cacheControl;
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2011 Fabien Potencier
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,245 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* ParameterBag is a container for key/value pairs.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class ParameterBag
{
protected $parameters;
/**
* Constructor.
*
* @param array $parameters An array of parameters
*
* @api
*/
public function __construct(array $parameters = array())
{
$this->parameters = $parameters;
}
/**
* Returns the parameters.
*
* @return array An array of parameters
*
* @api
*/
public function all()
{
return $this->parameters;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
*
* @api
*/
public function keys()
{
return array_keys($this->parameters);
}
/**
* Replaces the current parameters by a new set.
*
* @param array $parameters An array of parameters
*
* @api
*/
public function replace(array $parameters = array())
{
$this->parameters = $parameters;
}
/**
* Adds parameters.
*
* @param array $parameters An array of parameters
*
* @api
*/
public function add(array $parameters = array())
{
$this->parameters = array_replace($this->parameters, $parameters);
}
/**
* Returns a parameter by name.
*
* @param string $path The key
* @param mixed $default The default value
* @param boolean $deep
*
* @api
*/
public function get($path, $default = null, $deep = false)
{
if (!$deep || false === $pos = strpos($path, '[')) {
return array_key_exists($path, $this->parameters) ? $this->parameters[$path] : $default;
}
$root = substr($path, 0, $pos);
if (!array_key_exists($root, $this->parameters)) {
return $default;
}
$value = $this->parameters[$root];
$currentKey = null;
for ($i=$pos,$c=strlen($path); $i<$c; $i++) {
$char = $path[$i];
if ('[' === $char) {
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i));
}
$currentKey = '';
} else if (']' === $char) {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i));
}
if (!is_array($value) || !array_key_exists($currentKey, $value)) {
return $default;
}
$value = $value[$currentKey];
$currentKey = null;
} else {
if (null === $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i));
}
$currentKey .= $char;
}
}
if (null !== $currentKey) {
throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".'));
}
return $value;
}
/**
* Sets a parameter by name.
*
* @param string $key The key
* @param mixed $value The value
*
* @api
*/
public function set($key, $value)
{
$this->parameters[$key] = $value;
}
/**
* Returns true if the parameter is defined.
*
* @param string $key The key
*
* @return Boolean true if the parameter exists, false otherwise
*
* @api
*/
public function has($key)
{
return array_key_exists($key, $this->parameters);
}
/**
* Removes a parameter.
*
* @param string $key The key
*
* @api
*/
public function remove($key)
{
unset($this->parameters[$key]);
}
/**
* Returns the alphabetic characters of the parameter value.
*
* @param string $key The parameter key
* @param mixed $default The default value
* @param boolean $deep
*
* @return string The filtered value
*
* @api
*/
public function getAlpha($key, $default = '', $deep = false)
{
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep));
}
/**
* Returns the alphabetic characters and digits of the parameter value.
*
* @param string $key The parameter key
* @param mixed $default The default value
* @param boolean $deep
*
* @return string The filtered value
*
* @api
*/
public function getAlnum($key, $default = '', $deep = false)
{
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep));
}
/**
* Returns the digits of the parameter value.
*
* @param string $key The parameter key
* @param mixed $default The default value
* @param boolean $deep
*
* @return string The filtered value
*
* @api
*/
public function getDigits($key, $default = '', $deep = false)
{
return preg_replace('/[^[:digit:]]/', '', $this->get($key, $default, $deep));
}
/**
* Returns the parameter value converted to integer.
*
* @param string $key The parameter key
* @param mixed $default The default value
* @param boolean $deep
*
* @return string The filtered value
*
* @api
*/
public function getInt($key, $default = 0, $deep = false)
{
return (int) $this->get($key, $default, $deep);
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* RedirectResponse represents an HTTP response doing a redirect.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class RedirectResponse extends Response
{
/**
* Creates a redirect response so that it conforms to the rules defined for a redirect status code.
*
* @param string $url The URL to redirect to
* @param integer $status The status code (302 by default)
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3
*
* @api
*/
public function __construct($url, $status = 302)
{
if (empty($url)) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
}
parent::__construct(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="refresh" content="1;url=%1$s" />
<title>Redirecting to %1$s</title>
</head>
<body>
Redirecting to <a href="%1$s">%1$s</a>.
</body>
</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')),
$status,
array('Location' => $url)
);
if (!$this->isRedirect()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* RequestMatcher compares a pre-defined set of checks against a Request instance.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class RequestMatcher implements RequestMatcherInterface
{
private $path;
private $host;
private $methods;
private $ip;
private $attributes;
public function __construct($path = null, $host = null, $methods = null, $ip = null, array $attributes = array())
{
$this->path = $path;
$this->host = $host;
$this->methods = $methods;
$this->ip = $ip;
$this->attributes = $attributes;
}
/**
* Adds a check for the URL host name.
*
* @param string $regexp A Regexp
*/
public function matchHost($regexp)
{
$this->host = $regexp;
}
/**
* Adds a check for the URL path info.
*
* @param string $regexp A Regexp
*/
public function matchPath($regexp)
{
$this->path = $regexp;
}
/**
* Adds a check for the client IP.
*
* @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24
*/
public function matchIp($ip)
{
$this->ip = $ip;
}
/**
* Adds a check for the HTTP method.
*
* @param string|array $method An HTTP method or an array of HTTP methods
*/
public function matchMethod($method)
{
$this->methods = array_map('strtoupper', is_array($method) ? $method : array($method));
}
/**
* Adds a check for request attribute.
*
* @param string $key The request attribute name
* @param string $regexp A Regexp
*/
public function matchAttribute($key, $regexp)
{
$this->attributes[$key] = $regexp;
}
/**
* {@inheritdoc}
*
* @api
*/
public function matches(Request $request)
{
if (null !== $this->methods && !in_array($request->getMethod(), $this->methods)) {
return false;
}
foreach ($this->attributes as $key => $pattern) {
if (!preg_match('#'.str_replace('#', '\\#', $pattern).'#', $request->attributes->get($key))) {
return false;
}
}
if (null !== $this->path) {
$path = str_replace('#', '\\#', $this->path);
if (!preg_match('#'.$path.'#', $request->getPathInfo())) {
return false;
}
}
if (null !== $this->host && !preg_match('#'.str_replace('#', '\\#', $this->host).'#', $request->getHost())) {
return false;
}
if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) {
return false;
}
return true;
}
protected function checkIp($requestIp, $ip)
{
// IPv6 address
if (false !== strpos($requestIp, ':')) {
return $this->checkIp6($requestIp, $ip);
} else {
return $this->checkIp4($requestIp, $ip);
}
}
protected function checkIp4($requestIp, $ip)
{
if (false !== strpos($ip, '/')) {
list($address, $netmask) = explode('/', $ip);
if ($netmask < 1 || $netmask > 32) {
return false;
}
} else {
$address = $ip;
$netmask = 32;
}
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
}
/**
* @author David Soria Parra <dsp at php dot net>
* @see https://github.com/dsp/v6tools
*/
protected function checkIp6($requestIp, $ip)
{
if (!defined('AF_INET6')) {
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
}
list($address, $netmask) = explode('/', $ip);
$bytes_addr = unpack("n*", inet_pton($address));
$bytes_test = unpack("n*", inet_pton($requestIp));
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) {
$left = $netmask - 16 * ($i-1);
$left = ($left <= 16) ?: 16;
$mask = ~(0xffff >> $left) & 0xffff;
if (($bytes_addr[$i] & $mask) != ($bytes_test[$i] & $mask)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* RequestMatcherInterface is an interface for strategies to match a Request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
interface RequestMatcherInterface
{
/**
* Decides whether the rule(s) implemented by the strategy matches the supplied request.
*
* @param Request $request The request to check for a match
*
* @return Boolean true if the request matches, false otherwise
*
* @api
*/
function matches(Request $request);
}

View File

@ -0,0 +1,891 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* Response represents an HTTP response.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class Response
{
/**
* @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
*/
public $headers;
protected $content;
protected $version;
protected $statusCode;
protected $statusText;
protected $charset;
static public $statusTexts = array(
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
);
/**
* Constructor.
*
* @param string $content The response content
* @param integer $status The response status code
* @param array $headers An array of response headers
*
* @api
*/
public function __construct($content = '', $status = 200, $headers = array())
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
if (!$this->headers->has('Date')) {
$this->setDate(new \DateTime(null, new \DateTimeZone('UTC')));
}
}
/**
* Returns the response content as it will be sent (with the headers).
*
* @return string The response content
*/
public function __toString()
{
$this->prepare();
return
sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
$this->headers."\r\n".
$this->getContent();
}
/**
* Clones the current Response instance.
*/
public function __clone()
{
$this->headers = clone $this->headers;
}
/**
* Prepares the Response before it is sent to the client.
*
* This method tweaks the Response to ensure that it is
* compliant with RFC 2616.
*/
public function prepare()
{
if ($this->isInformational() || in_array($this->statusCode, array(204, 304))) {
$this->setContent('');
}
// Fix Content-Type
$charset = $this->charset ?: 'UTF-8';
if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', 'text/html; charset='.$charset);
} elseif ('text/' === substr($this->headers->get('Content-Type'), 0, 5) && false === strpos($this->headers->get('Content-Type'), 'charset')) {
// add the charset
$this->headers->set('Content-Type', $this->headers->get('Content-Type').'; charset='.$charset);
}
// Fix Content-Length
if ($this->headers->has('Transfer-Encoding')) {
$this->headers->remove('Content-Length');
}
}
/**
* Sends HTTP headers.
*/
public function sendHeaders()
{
// headers have already been sent by the developer
if (headers_sent()) {
return;
}
$this->prepare();
// status
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText));
// headers
foreach ($this->headers->all() as $name => $values) {
foreach ($values as $value) {
header($name.': '.$value, false);
}
}
// cookies
foreach ($this->headers->getCookies() as $cookie) {
setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
}
}
/**
* Sends content for the current web response.
*/
public function sendContent()
{
echo $this->content;
}
/**
* Sends HTTP headers and content.
*
* @api
*/
public function send()
{
$this->sendHeaders();
$this->sendContent();
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
}
/**
* Sets the response content
*
* Valid types are strings, numbers, and objects that implement a __toString() method.
*
* @param mixed $content
*
* @api
*/
public function setContent($content)
{
if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) {
throw new \UnexpectedValueException('The Response content must be a string or object implementing __toString(), "'.gettype($content).'" given.');
}
$this->content = (string) $content;
}
/**
* Gets the current response content
*
* @return string Content
*
* @api
*/
public function getContent()
{
return $this->content;
}
/**
* Sets the HTTP protocol version (1.0 or 1.1).
*
* @param string $version The HTTP protocol version
*
* @api
*/
public function setProtocolVersion($version)
{
$this->version = $version;
}
/**
* Gets the HTTP protocol version.
*
* @return string The HTTP protocol version
*
* @api
*/
public function getProtocolVersion()
{
return $this->version;
}
/**
* Sets response status code.
*
* @param integer $code HTTP status code
* @param string $text HTTP status text
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
*
* @api
*/
public function setStatusCode($code, $text = null)
{
$this->statusCode = (int) $code;
if ($this->isInvalid()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
}
$this->statusText = false === $text ? '' : (null === $text ? self::$statusTexts[$this->statusCode] : $text);
}
/**
* Retrieves status code for the current web response.
*
* @return string Status code
*
* @api
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* Sets response charset.
*
* @param string $charset Character set
*
* @api
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Retrieves the response charset.
*
* @return string Character set
*
* @api
*/
public function getCharset()
{
return $this->charset;
}
/**
* Returns true if the response is worth caching under any circumstance.
*
* Responses marked "private" with an explicit Cache-Control directive are
* considered uncacheable.
*
* Responses with neither a freshness lifetime (Expires, max-age) nor cache
* validator (Last-Modified, ETag) are considered uncacheable.
*
* @return Boolean true if the response is worth caching, false otherwise
*
* @api
*/
public function isCacheable()
{
if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
return false;
}
if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
return false;
}
return $this->isValidateable() || $this->isFresh();
}
/**
* Returns true if the response is "fresh".
*
* Fresh responses may be served from cache without any interaction with the
* origin. A response is considered fresh when it includes a Cache-Control/max-age
* indicator or Expiration header and the calculated age is less than the freshness lifetime.
*
* @return Boolean true if the response is fresh, false otherwise
*
* @api
*/
public function isFresh()
{
return $this->getTtl() > 0;
}
/**
* Returns true if the response includes headers that can be used to validate
* the response with the origin server using a conditional GET request.
*
* @return Boolean true if the response is validateable, false otherwise
*
* @api
*/
public function isValidateable()
{
return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
}
/**
* Marks the response as "private".
*
* It makes the response ineligible for serving other clients.
*
* @api
*/
public function setPrivate()
{
$this->headers->removeCacheControlDirective('public');
$this->headers->addCacheControlDirective('private');
}
/**
* Marks the response as "public".
*
* It makes the response eligible for serving other clients.
*
* @api
*/
public function setPublic()
{
$this->headers->addCacheControlDirective('public');
$this->headers->removeCacheControlDirective('private');
}
/**
* Returns true if the response must be revalidated by caches.
*
* This method indicates that the response must not be served stale by a
* cache in any circumstance without first revalidating with the origin.
* When present, the TTL of the response should not be overridden to be
* greater than the value provided by the origin.
*
* @return Boolean true if the response must be revalidated by a cache, false otherwise
*
* @api
*/
public function mustRevalidate()
{
return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->has('must-proxy-revalidate');
}
/**
* Returns the Date header as a DateTime instance.
*
* @return \DateTime A \DateTime instance
*
* @throws \RuntimeException when the header is not parseable
*
* @api
*/
public function getDate()
{
return $this->headers->getDate('Date');
}
/**
* Sets the Date header.
*
* @param \DateTime $date A \DateTime instance
*
* @api
*/
public function setDate(\DateTime $date)
{
$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
}
/**
* Returns the age of the response.
*
* @return integer The age of the response in seconds
*/
public function getAge()
{
if ($age = $this->headers->get('Age')) {
return $age;
}
return max(time() - $this->getDate()->format('U'), 0);
}
/**
* Marks the response stale by setting the Age header to be equal to the maximum age of the response.
*
* @api
*/
public function expire()
{
if ($this->isFresh()) {
$this->headers->set('Age', $this->getMaxAge());
}
}
/**
* Returns the value of the Expires header as a DateTime instance.
*
* @return \DateTime A DateTime instance
*
* @api
*/
public function getExpires()
{
return $this->headers->getDate('Expires');
}
/**
* Sets the Expires HTTP header with a \DateTime instance.
*
* If passed a null value, it removes the header.
*
* @param \DateTime $date A \DateTime instance
*
* @api
*/
public function setExpires(\DateTime $date = null)
{
if (null === $date) {
$this->headers->remove('Expires');
} else {
$date = clone $date;
$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
}
}
/**
* Sets the number of seconds after the time specified in the response's Date
* header when the the response should no longer be considered fresh.
*
* First, it checks for a s-maxage directive, then a max-age directive, and then it falls
* back on an expires header. It returns null when no maximum age can be established.
*
* @return integer|null Number of seconds
*
* @api
*/
public function getMaxAge()
{
if ($age = $this->headers->getCacheControlDirective('s-maxage')) {
return $age;
}
if ($age = $this->headers->getCacheControlDirective('max-age')) {
return $age;
}
if (null !== $this->getExpires()) {
return $this->getExpires()->format('U') - $this->getDate()->format('U');
}
return null;
}
/**
* Sets the number of seconds after which the response should no longer be considered fresh.
*
* This methods sets the Cache-Control max-age directive.
*
* @param integer $value A number of seconds
*
* @api
*/
public function setMaxAge($value)
{
$this->headers->addCacheControlDirective('max-age', $value);
}
/**
* Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
*
* This methods sets the Cache-Control s-maxage directive.
*
* @param integer $value A number of seconds
*
* @api
*/
public function setSharedMaxAge($value)
{
$this->setPublic();
$this->headers->addCacheControlDirective('s-maxage', $value);
}
/**
* Returns the response's time-to-live in seconds.
*
* It returns null when no freshness information is present in the response.
*
* When the responses TTL is <= 0, the response may not be served from cache without first
* revalidating with the origin.
*
* @return integer The TTL in seconds
*
* @api
*/
public function getTtl()
{
if ($maxAge = $this->getMaxAge()) {
return $maxAge - $this->getAge();
}
return null;
}
/**
* Sets the response's time-to-live for shared caches.
*
* This method adjusts the Cache-Control/s-maxage directive.
*
* @param integer $seconds The number of seconds
*
* @api
*/
public function setTtl($seconds)
{
$this->setSharedMaxAge($this->getAge() + $seconds);
}
/**
* Sets the response's time-to-live for private/client caches.
*
* This method adjusts the Cache-Control/max-age directive.
*
* @param integer $seconds The number of seconds
*
* @api
*/
public function setClientTtl($seconds)
{
$this->setMaxAge($this->getAge() + $seconds);
}
/**
* Returns the Last-Modified HTTP header as a DateTime instance.
*
* @return \DateTime A DateTime instance
*
* @api
*/
public function getLastModified()
{
return $this->headers->getDate('Last-Modified');
}
/**
* Sets the Last-Modified HTTP header with a \DateTime instance.
*
* If passed a null value, it removes the header.
*
* @param \DateTime $date A \DateTime instance
*
* @api
*/
public function setLastModified(\DateTime $date = null)
{
if (null === $date) {
$this->headers->remove('Last-Modified');
} else {
$date = clone $date;
$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
}
}
/**
* Returns the literal value of ETag HTTP header.
*
* @return string The ETag HTTP header
*
* @api
*/
public function getEtag()
{
return $this->headers->get('ETag');
}
/**
* Sets the ETag value.
*
* @param string $etag The ETag unique identifier
* @param Boolean $weak Whether you want a weak ETag or not
*
* @api
*/
public function setEtag($etag = null, $weak = false)
{
if (null === $etag) {
$this->headers->remove('Etag');
} else {
if (0 !== strpos($etag, '"')) {
$etag = '"'.$etag.'"';
}
$this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
}
}
/**
* Sets Response cache headers (validation and/or expiration).
*
* Available options are: etag, last_modified, max_age, s_maxage, private, and public.
*
* @param array $options An array of cache options
*
* @api
*/
public function setCache(array $options)
{
if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) {
throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_keys($diff))));
}
if (isset($options['etag'])) {
$this->setEtag($options['etag']);
}
if (isset($options['last_modified'])) {
$this->setLastModified($options['last_modified']);
}
if (isset($options['max_age'])) {
$this->setMaxAge($options['max_age']);
}
if (isset($options['s_maxage'])) {
$this->setSharedMaxAge($options['s_maxage']);
}
if (isset($options['public'])) {
if ($options['public']) {
$this->setPublic();
} else {
$this->setPrivate();
}
}
if (isset($options['private'])) {
if ($options['private']) {
$this->setPrivate();
} else {
$this->setPublic();
}
}
}
/**
* Modifies the response so that it conforms to the rules defined for a 304 status code.
*
* This sets the status, removes the body, and discards any headers
* that MUST NOT be included in 304 responses.
*
* @see http://tools.ietf.org/html/rfc2616#section-10.3.5
*
* @api
*/
public function setNotModified()
{
$this->setStatusCode(304);
$this->setContent(null);
// remove headers that MUST NOT be included with 304 Not Modified responses
foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
$this->headers->remove($header);
}
}
/**
* Returns true if the response includes a Vary header.
*
* @return true if the response includes a Vary header, false otherwise
*
* @api
*/
public function hasVary()
{
return (Boolean) $this->headers->get('Vary');
}
/**
* Returns an array of header names given in the Vary header.
*
* @return array An array of Vary names
*
* @api
*/
public function getVary()
{
if (!$vary = $this->headers->get('Vary')) {
return array();
}
return is_array($vary) ? $vary : preg_split('/[\s,]+/', $vary);
}
/**
* Sets the Vary header.
*
* @param string|array $headers
* @param Boolean $replace Whether to replace the actual value of not (true by default)
*
* @api
*/
public function setVary($headers, $replace = true)
{
$this->headers->set('Vary', $headers, $replace);
}
/**
* Determines if the Response validators (ETag, Last-Modified) matches
* a conditional value specified in the Request.
*
* If the Response is not modified, it sets the status code to 304 and
* remove the actual content by calling the setNotModified() method.
*
* @param Request $request A Request instance
*
* @return Boolean true if the Response validators matches the Request, false otherwise
*
* @api
*/
public function isNotModified(Request $request)
{
$lastModified = $request->headers->get('If-Modified-Since');
$notModified = false;
if ($etags = $request->getEtags()) {
$notModified = (in_array($this->getEtag(), $etags) || in_array('*', $etags)) && (!$lastModified || $this->headers->get('Last-Modified') == $lastModified);
} elseif ($lastModified) {
$notModified = $lastModified == $this->headers->get('Last-Modified');
}
if ($notModified) {
$this->setNotModified();
}
return $notModified;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
/**
* @api
*/
public function isInvalid()
{
return $this->statusCode < 100 || $this->statusCode >= 600;
}
/**
* @api
*/
public function isInformational()
{
return $this->statusCode >= 100 && $this->statusCode < 200;
}
/**
* @api
*/
public function isSuccessful()
{
return $this->statusCode >= 200 && $this->statusCode < 300;
}
/**
* @api
*/
public function isRedirection()
{
return $this->statusCode >= 300 && $this->statusCode < 400;
}
/**
* @api
*/
public function isClientError()
{
return $this->statusCode >= 400 && $this->statusCode < 500;
}
/**
* @api
*/
public function isServerError()
{
return $this->statusCode >= 500 && $this->statusCode < 600;
}
/**
* @api
*/
public function isOk()
{
return 200 === $this->statusCode;
}
/**
* @api
*/
public function isForbidden()
{
return 403 === $this->statusCode;
}
/**
* @api
*/
public function isNotFound()
{
return 404 === $this->statusCode;
}
/**
* @api
*/
public function isRedirect($location = null)
{
return in_array($this->statusCode, array(201, 301, 302, 303, 307)) && (null === $location ?: $location == $this->headers->get('Location'));
}
/**
* @api
*/
public function isEmpty()
{
return in_array($this->statusCode, array(201, 204, 304));
}
}

View File

@ -0,0 +1,238 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* ResponseHeaderBag is a container for Response HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class ResponseHeaderBag extends HeaderBag
{
const COOKIES_FLAT = 'flat';
const COOKIES_ARRAY = 'array';
protected $computedCacheControl = array();
protected $cookies = array();
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
*
* @api
*/
public function __construct(array $headers = array())
{
parent::__construct($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('cache-control', '');
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
$cookies = '';
foreach ($this->getCookies() as $cookie) {
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
}
return parent::__toString().$cookies;
}
/**
* {@inheritdoc}
*
* @api
*/
public function replace(array $headers = array())
{
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('cache-control', '');
}
}
/**
* {@inheritdoc}
*
* @api
*/
public function set($key, $values, $replace = true)
{
parent::set($key, $values, $replace);
// ensure the cache-control header has sensible defaults
if (in_array(strtr(strtolower($key), '_', '-'), array('cache-control', 'etag', 'last-modified', 'expires'))) {
$computed = $this->computeCacheControlValue();
$this->headers['cache-control'] = array($computed);
$this->computedCacheControl = $this->parseCacheControl($computed);
}
}
/**
* {@inheritdoc}
*
* @api
*/
public function remove($key)
{
parent::remove($key);
if ('cache-control' === strtr(strtolower($key), '_', '-')) {
$this->computedCacheControl = array();
}
}
/**
* {@inheritdoc}
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl);
}
/**
* {@inheritdoc}
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
/**
* Sets a cookie.
*
* @param Cookie $cookie
* @return void
*
* @api
*/
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
}
/**
* Removes a cookie from the array, but does not unset it in the browser
*
* @param string $name
* @param string $path
* @param string $domain
* @return void
*
* @api
*/
public function removeCookie($name, $path = '/', $domain = null)
{
if (null === $path) {
$path = '/';
}
unset($this->cookies[$domain][$path][$name]);
if (empty($this->cookies[$domain][$path])) {
unset($this->cookies[$domain][$path]);
if (empty($this->cookies[$domain])) {
unset($this->cookies[$domain]);
}
}
}
/**
* Returns an array with all cookies
*
* @param string $format
*
* @throws \InvalidArgumentException When the $format is invalid
*
* @return array
*
* @api
*/
public function getCookies($format = self::COOKIES_FLAT)
{
if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
}
if (self::COOKIES_ARRAY === $format) {
return $this->cookies;
}
$flattenedCookies = array();
foreach ($this->cookies as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
$flattenedCookies[] = $cookie;
}
}
}
return $flattenedCookies;
}
/**
* Clears a cookie in the browser
*
* @param string $name
* @param string $path
* @param string $domain
* @return void
*
* @api
*/
public function clearCookie($name, $path = '/', $domain = null)
{
$this->setCookie(new Cookie($name, null, 1, $path, $domain));
}
/**
* Returns the calculated value of the cache-control header.
*
* This considers several other headers and calculates or modifies the
* cache-control header to a sensible, conservative value.
*
* @return string
*/
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache';
}
if (!$this->cacheControl) {
// conservative by default
return 'private, must-revalidate';
}
$header = $this->getCacheControlHeader();
if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
return $header;
}
// public if s-maxage is defined, private otherwise
if (!isset($this->cacheControl['s-maxage'])) {
return $header.', private';
}
return $header;
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* ServerBag is a container for HTTP headers from the $_SERVER variable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
*/
class ServerBag extends ParameterBag
{
public function getHeaders()
{
$headers = array();
foreach ($this->parameters as $key => $value) {
if ('HTTP_' === substr($key, 0, 5)) {
$headers[substr($key, 5)] = $value;
}
// CONTENT_* are not prefixed with HTTP_
elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) {
$headers[$key] = $this->parameters[$key];
}
}
// PHP_AUTH_USER/PHP_AUTH_PW
if (isset($this->parameters['PHP_AUTH_USER'])) {
$pass = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : '';
$headers['AUTHORIZATION'] = 'Basic '.base64_encode($this->parameters['PHP_AUTH_USER'].':'.$pass);
}
return $headers;
}
}

View File

@ -0,0 +1,405 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface;
/**
* Session.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class Session implements \Serializable
{
protected $storage;
protected $started;
protected $attributes;
protected $flashes;
protected $oldFlashes;
protected $locale;
protected $defaultLocale;
protected $closed;
/**
* Constructor.
*
* @param SessionStorageInterface $storage A SessionStorageInterface instance
* @param string $defaultLocale The default locale
*/
public function __construct(SessionStorageInterface $storage, $defaultLocale = 'en')
{
$this->storage = $storage;
$this->defaultLocale = $defaultLocale;
$this->locale = $defaultLocale;
$this->flashes = array();
$this->oldFlashes = array();
$this->attributes = array();
$this->setPhpDefaultLocale($this->defaultLocale);
$this->started = false;
$this->closed = false;
}
/**
* Starts the session storage.
*
* @api
*/
public function start()
{
if (true === $this->started) {
return;
}
$this->storage->start();
$attributes = $this->storage->read('_symfony2');
if (isset($attributes['attributes'])) {
$this->attributes = $attributes['attributes'];
$this->flashes = $attributes['flashes'];
$this->locale = $attributes['locale'];
$this->setPhpDefaultLocale($this->locale);
// flag current flash messages to be removed at shutdown
$this->oldFlashes = $this->flashes;
}
$this->started = true;
}
/**
* Checks if an attribute is defined.
*
* @param string $name The attribute name
*
* @return Boolean true if the attribute is defined, false otherwise
*
* @api
*/
public function has($name)
{
return array_key_exists($name, $this->attributes);
}
/**
* Returns an attribute.
*
* @param string $name The attribute name
* @param mixed $default The default value
*
* @return mixed
*
* @api
*/
public function get($name, $default = null)
{
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
/**
* Sets an attribute.
*
* @param string $name
* @param mixed $value
*
* @api
*/
public function set($name, $value)
{
if (false === $this->started) {
$this->start();
}
$this->attributes[$name] = $value;
}
/**
* Returns attributes.
*
* @return array Attributes
*
* @api
*/
public function all()
{
return $this->attributes;
}
/**
* Sets attributes.
*
* @param array $attributes Attributes
*
* @api
*/
public function replace(array $attributes)
{
if (false === $this->started) {
$this->start();
}
$this->attributes = $attributes;
}
/**
* Removes an attribute.
*
* @param string $name
*
* @api
*/
public function remove($name)
{
if (false === $this->started) {
$this->start();
}
if (array_key_exists($name, $this->attributes)) {
unset($this->attributes[$name]);
}
}
/**
* Clears all attributes.
*
* @api
*/
public function clear()
{
if (false === $this->started) {
$this->start();
}
$this->attributes = array();
$this->flashes = array();
$this->setPhpDefaultLocale($this->locale = $this->defaultLocale);
}
/**
* Invalidates the current session.
*
* @api
*/
public function invalidate()
{
$this->clear();
$this->storage->regenerate(true);
}
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @api
*/
public function migrate()
{
$this->storage->regenerate();
}
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @api
*/
public function getId()
{
if (false === $this->started) {
$this->start();
}
return $this->storage->getId();
}
/**
* Returns the locale
*
* @return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* Sets the locale.
*
* @param string $locale
*/
public function setLocale($locale)
{
if (false === $this->started) {
$this->start();
}
$this->setPhpDefaultLocale($this->locale = $locale);
}
/**
* Gets the flash messages.
*
* @return array
*/
public function getFlashes()
{
return $this->flashes;
}
/**
* Sets the flash messages.
*
* @param array $values
*/
public function setFlashes($values)
{
if (false === $this->started) {
$this->start();
}
$this->flashes = $values;
$this->oldFlashes = array();
}
/**
* Gets a flash message.
*
* @param string $name
* @param string|null $default
*
* @return string
*/
public function getFlash($name, $default = null)
{
return array_key_exists($name, $this->flashes) ? $this->flashes[$name] : $default;
}
/**
* Sets a flash message.
*
* @param string $name
* @param string $value
*/
public function setFlash($name, $value)
{
if (false === $this->started) {
$this->start();
}
$this->flashes[$name] = $value;
unset($this->oldFlashes[$name]);
}
/**
* Checks whether a flash message exists.
*
* @param string $name
*
* @return Boolean
*/
public function hasFlash($name)
{
if (false === $this->started) {
$this->start();
}
return array_key_exists($name, $this->flashes);
}
/**
* Removes a flash message.
*
* @param string $name
*/
public function removeFlash($name)
{
if (false === $this->started) {
$this->start();
}
unset($this->flashes[$name]);
}
/**
* Removes the flash messages.
*/
public function clearFlashes()
{
if (false === $this->started) {
$this->start();
}
$this->flashes = array();
$this->oldFlashes = array();
}
public function save()
{
if (false === $this->started) {
$this->start();
}
$this->flashes = array_diff_key($this->flashes, $this->oldFlashes);
$this->storage->write('_symfony2', array(
'attributes' => $this->attributes,
'flashes' => $this->flashes,
'locale' => $this->locale,
));
}
/**
* This method should be called when you don't want the session to be saved
* when the Session object is garbaged collected (useful for instance when
* you want to simulate the interaction of several users/sessions in a single
* PHP process).
*/
public function close()
{
$this->closed = true;
}
public function __destruct()
{
if (true === $this->started && !$this->closed) {
$this->save();
}
}
public function serialize()
{
return serialize(array($this->storage, $this->defaultLocale));
}
public function unserialize($serialized)
{
list($this->storage, $this->defaultLocale) = unserialize($serialized);
$this->attributes = array();
$this->started = false;
}
private function setPhpDefaultLocale($locale)
{
// if either the class Locale doesn't exist, or an exception is thrown when
// setting the default locale, the intl module is not installed, and
// the call can be ignored:
try {
if (class_exists('Locale', false)) {
\Locale::setDefault($locale);
}
} catch (\Exception $e) {
}
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* ArraySessionStorage mocks the session for unit tests.
*
* When doing functional testing, you should use FilesystemSessionStorage instead.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
*/
class ArraySessionStorage implements SessionStorageInterface
{
private $data = array();
public function read($key, $default = null)
{
return array_key_exists($key, $this->data) ? $this->data[$key] : $default;
}
public function regenerate($destroy = false)
{
if ($destroy) {
$this->data = array();
}
return true;
}
public function remove($key)
{
unset($this->data[$key]);
}
public function start()
{
}
public function getId()
{
}
public function write($key, $data)
{
$this->data[$key] = $data;
}
}

View File

@ -0,0 +1,174 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* FilesystemSessionStorage simulates sessions for functional tests.
*
* This storage does not start the session (session_start())
* as it is not "available" when running tests on the command line.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class FilesystemSessionStorage extends NativeSessionStorage
{
private $path;
private $data;
private $started;
/**
* Constructor.
*/
public function __construct($path, array $options = array())
{
$this->path = $path;
$this->started = false;
parent::__construct($options);
}
/**
* Starts the session.
*
* @api
*/
public function start()
{
if ($this->started) {
return;
}
session_set_cookie_params(
$this->options['lifetime'],
$this->options['path'],
$this->options['domain'],
$this->options['secure'],
$this->options['httponly']
);
if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) {
session_id($this->options['id']);
}
if (!session_id()) {
session_id(hash('md5', uniqid(mt_rand(), true)));
}
$file = $this->path.'/'.session_id().'.session';
$this->data = file_exists($file) ? unserialize(file_get_contents($file)) : array();
$this->started = true;
}
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @throws \RuntimeException If the session was not started yet
*
* @api
*/
public function getId()
{
if (!$this->started) {
throw new \RuntimeException('The session must be started before reading its ID');
}
return session_id();
}
/**
* Reads data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
*
* @return mixed Data associated with the key
*
* @throws \RuntimeException If an error occurs while reading data from this storage
*
* @api
*/
public function read($key, $default = null)
{
return array_key_exists($key, $this->data) ? $this->data[$key] : $default;
}
/**
* Removes data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
*
* @return mixed Data associated with the key
*
* @throws \RuntimeException If an error occurs while removing data from this storage
*
* @api
*/
public function remove($key)
{
$retval = $this->data[$key];
unset($this->data[$key]);
return $retval;
}
/**
* Writes data to this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
* @param mixed $data Data associated with your key
*
* @throws \RuntimeException If an error occurs while writing to this storage
*
* @api
*/
public function write($key, $data)
{
$this->data[$key] = $data;
if (!is_dir($this->path)) {
mkdir($this->path, 0777, true);
}
file_put_contents($this->path.'/'.session_id().'.session', serialize($this->data));
}
/**
* Regenerates id that represents this storage.
*
* @param Boolean $destroy Destroy session when regenerating?
*
* @return Boolean True if session regenerated, false if error
*
* @throws \RuntimeException If an error occurs while regenerating this storage
*
* @api
*/
public function regenerate($destroy = false)
{
if ($destroy) {
$this->data = array();
}
return true;
}
}

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* NativeSessionStorage.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class NativeSessionStorage implements SessionStorageInterface
{
static protected $sessionIdRegenerated = false;
static protected $sessionStarted = false;
protected $options;
/**
* Available options:
*
* * name: The cookie name (null [omitted] by default)
* * id: The session id (null [omitted] by default)
* * lifetime: Cookie lifetime
* * path: Cookie path
* * domain: Cookie domain
* * secure: Cookie secure
* * httponly: Cookie http only
*
* The default values for most options are those returned by the session_get_cookie_params() function
*
* @param array $options An associative array of session options
*/
public function __construct(array $options = array())
{
$cookieDefaults = session_get_cookie_params();
$this->options = array_merge(array(
'lifetime' => $cookieDefaults['lifetime'],
'path' => $cookieDefaults['path'],
'domain' => $cookieDefaults['domain'],
'secure' => $cookieDefaults['secure'],
'httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false,
), $options);
// Skip setting new session name if user don't want it
if (isset($this->options['name'])) {
session_name($this->options['name']);
}
}
/**
* Starts the session.
*
* @api
*/
public function start()
{
if (self::$sessionStarted) {
return;
}
session_set_cookie_params(
$this->options['lifetime'],
$this->options['path'],
$this->options['domain'],
$this->options['secure'],
$this->options['httponly']
);
// disable native cache limiter as this is managed by HeaderBag directly
session_cache_limiter(false);
if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) {
session_id($this->options['id']);
}
session_start();
self::$sessionStarted = true;
}
/**
* {@inheritDoc}
*
* @api
*/
public function getId()
{
if (!self::$sessionStarted) {
throw new \RuntimeException('The session must be started before reading its ID');
}
return session_id();
}
/**
* Reads data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
* @param string $default Default value
*
* @return mixed Data associated with the key
*
* @api
*/
public function read($key, $default = null)
{
return array_key_exists($key, $_SESSION) ? $_SESSION[$key] : $default;
}
/**
* Removes data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
*
* @return mixed Data associated with the key
*
* @api
*/
public function remove($key)
{
$retval = null;
if (isset($_SESSION[$key])) {
$retval = $_SESSION[$key];
unset($_SESSION[$key]);
}
return $retval;
}
/**
* Writes data to this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
* @param mixed $data Data associated with your key
*
* @api
*/
public function write($key, $data)
{
$_SESSION[$key] = $data;
}
/**
* Regenerates id that represents this storage.
*
* @param Boolean $destroy Destroy session when regenerating?
*
* @return Boolean True if session regenerated, false if error
*
* @api
*/
public function regenerate($destroy = false)
{
if (self::$sessionIdRegenerated) {
return;
}
session_regenerate_id($destroy);
self::$sessionIdRegenerated = true;
}
}

View File

@ -0,0 +1,263 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* PdoSessionStorage.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Michael Williams <michael.williams@funsational.com>
*/
class PdoSessionStorage extends NativeSessionStorage
{
private $db;
private $dbOptions;
/**
* Constructor.
*
* @param \PDO $db A PDO instance
* @param array $options An associative array of session options
* @param array $dbOptions An associative array of DB options
*
* @throws \InvalidArgumentException When "db_table" option is not provided
*
* @see NativeSessionStorage::__construct()
*/
public function __construct(\PDO $db, array $options = array(), array $dbOptions = array())
{
if (!array_key_exists('db_table', $dbOptions)) {
throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.');
}
$this->db = $db;
$this->dbOptions = array_merge(array(
'db_id_col' => 'sess_id',
'db_data_col' => 'sess_data',
'db_time_col' => 'sess_time',
), $dbOptions);
parent::__construct($options);
}
/**
* Starts the session.
*/
public function start()
{
if (self::$sessionStarted) {
return;
}
// use this object as the session handler
session_set_save_handler(
array($this, 'sessionOpen'),
array($this, 'sessionClose'),
array($this, 'sessionRead'),
array($this, 'sessionWrite'),
array($this, 'sessionDestroy'),
array($this, 'sessionGC')
);
parent::start();
}
/**
* Opens a session.
*
* @param string $path (ignored)
* @param string $name (ignored)
*
* @return Boolean true, if the session was opened, otherwise an exception is thrown
*/
public function sessionOpen($path = null, $name = null)
{
return true;
}
/**
* Closes a session.
*
* @return Boolean true, if the session was closed, otherwise false
*/
public function sessionClose()
{
// do nothing
return true;
}
/**
* Destroys a session.
*
* @param string $id A session ID
*
* @return Boolean true, if the session was destroyed, otherwise an exception is thrown
*
* @throws \RuntimeException If the session cannot be destroyed
*/
public function sessionDestroy($id)
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbIdCol = $this->dbOptions['db_id_col'];
// delete the record associated with this id
$sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id";
try {
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->execute();
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* Cleans up old sessions.
*
* @param int $lifetime The lifetime of a session
*
* @return Boolean true, if old sessions have been cleaned, otherwise an exception is thrown
*
* @throws \RuntimeException If any old sessions cannot be cleaned
*/
public function sessionGC($lifetime)
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbTimeCol = $this->dbOptions['db_time_col'];
// delete the record associated with this id
$sql = "DELETE FROM $dbTable WHERE $dbTimeCol < (:time - $lifetime)";
try {
$this->db->query($sql);
$stmt = $this->db->prepare($sql);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* Reads a session.
*
* @param string $id A session ID
*
* @return string The session data if the session was read or created, otherwise an exception is thrown
*
* @throws \RuntimeException If the session cannot be read
*/
public function sessionRead($id)
{
// get table/columns
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
try {
$sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR, 255);
$stmt->execute();
// it is recommended to use fetchAll so that PDO can close the DB cursor
// we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777
$sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM);
if (count($sessionRows) == 1) {
return $sessionRows[0][0];
}
// session does not exist, create it
$this->createNewSession($id);
return '';
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
}
}
/**
* Writes session data.
*
* @param string $id A session ID
* @param string $data A serialized chunk of session data
*
* @return Boolean true, if the session was written, otherwise an exception is thrown
*
* @throws \RuntimeException If the session data cannot be written
*/
public function sessionWrite($id, $data)
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$sql = ('mysql' === $this->db->getAttribute(\PDO::ATTR_DRIVER_NAME))
? "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) "
."ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = CASE WHEN $dbTimeCol = :time THEN (VALUES($dbTimeCol) + 1) ELSE VALUES($dbTimeCol) END"
: "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id";
try {
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $data, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
if (!$stmt->rowCount()) {
// No session exists in the database to update. This happens when we have called
// session_regenerate_id()
$this->createNewSession($id, $data);
}
} catch (\PDOException $e) {
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
}
return true;
}
/**
* Creates a new session with the given $id and $data
*
* @param string $id
* @param string $data
*/
private function createNewSession($id, $data = '')
{
// get table/column
$dbTable = $this->dbOptions['db_table'];
$dbDataCol = $this->dbOptions['db_data_col'];
$dbIdCol = $this->dbOptions['db_id_col'];
$dbTimeCol = $this->dbOptions['db_time_col'];
$sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time)";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':id', $id, \PDO::PARAM_STR);
$stmt->bindParam(':data', $data, \PDO::PARAM_STR);
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
$stmt->execute();
return true;
}
}

View File

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\SessionStorage;
/**
* SessionStorageInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
interface SessionStorageInterface
{
/**
* Starts the session.
*
* @api
*/
function start();
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @throws \RuntimeException If the session was not started yet
*
* @api
*/
function getId();
/**
* Reads data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
*
* @return mixed Data associated with the key
*
* @throws \RuntimeException If an error occurs while reading data from this storage
*
* @api
*/
function read($key);
/**
* Removes data from this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
*
* @return mixed Data associated with the key
*
* @throws \RuntimeException If an error occurs while removing data from this storage
*
* @api
*/
function remove($key);
/**
* Writes data to this storage.
*
* The preferred format for a key is directory style so naming conflicts can be avoided.
*
* @param string $key A unique key identifying your data
* @param mixed $data Data associated with your key
*
* @throws \RuntimeException If an error occurs while writing to this storage
*
* @api
*/
function write($key, $data);
/**
* Regenerates id that represents this storage.
*
* @param Boolean $destroy Destroy session when regenerating?
*
* @return Boolean True if session regenerated, false if error
*
* @throws \RuntimeException If an error occurs while regenerating this storage
*
* @api
*/
function regenerate($destroy = false);
}

View File

@ -0,0 +1,22 @@
{
"name": "symfony/http-foundation",
"type": "library",
"description": "Symfony HttpFoundation Component",
"keywords": [],
"homepage": "http://symfony.com",
"version": "2.0.4",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.2"
}
}

View File

@ -2228,6 +2228,38 @@ function _drupal_bootstrap_configuration() {
timer_start('page');
// Initialize the configuration, including variables from settings.php.
drupal_settings_initialize();
// Hook up the Symfony ClassLoader for loading PSR-0-compatible classes.
require_once(DRUPAL_ROOT . '/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php');
// By default, use the UniversalClassLoader which is best for development,
// as it does not break when code is moved on the file system. It is slow,
// however, so for production the APC class loader should be used instead.
// @todo Switch to a cleaner way to switch autoloaders than variable_get().
switch (variable_get('autoloader_mode', 'default')) {
case 'apc':
if (function_exists('apc_store')) {
require_once(DRUPAL_ROOT . '/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php');
$loader = new \Symfony\Component\ClassLoader\ApcUniversalClassLoader('drupal.' . $GLOBALS['drupal_hash_salt']);
break;
}
// If APC was not loaded, fall through to the default loader so that
// the site does not fail completely.
case 'dev':
case 'default':
default:
$loader = new \Symfony\Component\ClassLoader\UniversalClassLoader();
break;
}
// Register classes with namespaces.
$loader->registerNamespaces(array(
// All Symfony-borrowed code lives in /includes/Symfony.
'Symfony' => DRUPAL_ROOT . '/includes',
));
// Activate the autoloader.
$loader->register();
}
/**

View File

@ -30,6 +30,7 @@ files[] = tests/path.test
files[] = tests/registry.test
files[] = tests/schema.test
files[] = tests/session.test
files[] = tests/symfony.test
files[] = tests/tablesort.test
files[] = tests/theme.test
files[] = tests/unicode.test

View File

@ -0,0 +1,31 @@
<?php
/**
* @file
* Tests for Symfony2-related functionality.
*/
/**
* Tests related to Symfony class loading.
*/
class SymfonyClassLoaderTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
'name' => 'Class loader',
'description' => 'Confirm that the PSR-0 class loader is connected properly',
'group' => 'Symfony',
);
}
function setUp() {
parent::setUp();
}
/**
* Test that we can lazy-load classes from the Symfony framework.
*/
function testClassesLoad() {
$class_name = 'Symfony\\Component\\HttpFoundation\\Request';
$this->assertTrue(class_exists($class_name), t('Class !class_name exists', array('!class_name' => $class_name)));
}
}