[OutputEscaper] refactored the component

This commit is contained in:
Fabien Potencier 2010-10-26 21:14:43 +02:00
parent e1116524ed
commit c065be88b5
11 changed files with 210 additions and 359 deletions

View File

@ -18,7 +18,7 @@ namespace Symfony\Component\OutputEscaper;
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
class ArrayDecorator extends GetterDecorator implements \Iterator, \ArrayAccess, \Countable
class ArrayDecorator extends BaseEscaper implements \Iterator, \ArrayAccess, \Countable
{
/**
* Used by the iterator to know if the current element is valid.
@ -141,6 +141,17 @@ class ArrayDecorator extends GetterDecorator implements \Iterator, \ArrayAccess,
throw new \LogicException('Cannot unset values.');
}
/**
* Escapes a key from the array using the specified escaper.
*
* @param string $key The array key
* @param mixed $escaper The escaping method (a PHP callable or a named escaper)
*/
public function getEscapedKey($key, $escaper)
{
return Escaper::escape($escaper, $this->value[$key]);
}
/**
* Returns the size of the array (are required by the Countable interface).
*
@ -150,16 +161,4 @@ class ArrayDecorator extends GetterDecorator implements \Iterator, \ArrayAccess,
{
return count($this->value);
}
/**
* Returns the (unescaped) value from the array associated with the key supplied.
*
* @param string $key The key into the array to use
*
* @return mixed The value
*/
public function getRaw($key)
{
return $this->value[$key];
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Symfony\Component\OutputEscaper;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract class that provides an interface for output escaping.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
abstract class BaseEscaper
{
/**
* The value that is to be escaped.
*/
protected $value;
/**
* The escaper (a PHP callable or a named escaper) that is going to be applied to the value and its children.
*/
protected $escaper;
/**
* Constructor.
*
* Since BaseEscaper is an abstract class, instances cannot be created
* directly but the constructor will be inherited by sub-classes.
*
* @param mixed $escaper The escaping method (a PHP callable or a named escaper)
* @param string $value Escaping value
*/
public function __construct($escaper, $value)
{
$this->escaper = $escaper;
$this->value = $value;
}
/**
* Sets the default escaper to use.
*
* @param mixed $escaper The escaping method (a PHP callable or a named escaper)
*/
public function setEscaper($escaper)
{
$this->escaper = $escaper;
}
/**
* Returns the raw value associated with this instance.
*
* Concrete instances of BaseEscaper classes decorate a value which is
* stored by the constructor. This returns that original, unescaped, value.
*
* @return mixed The original value used to construct the decorator
*/
public function getRawValue()
{
return $this->value;
}
}

View File

@ -12,51 +12,17 @@ namespace Symfony\Component\OutputEscaper;
*/
/**
* Abstract class that provides an interface for escaping of output.
* Escaper provides output escaping features.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
abstract class Escaper
class Escaper
{
/**
* The value that is to be escaped.
*
* @var mixed
*/
protected $value;
/**
* The escaper (a PHP callable) that is going to be applied to the value and its
* children.
*
* @var string
*/
protected $escaper;
static protected $charset = 'UTF-8';
static protected $safeClasses = array();
static protected $escapers;
/**
* Constructor.
*
* Since Escaper is an abstract class, instances cannot be created
* directly but the constructor will be inherited by sub-classes.
*
* @param string $callable A PHP callable
* @param string $value Escaping value
*/
public function __construct($escaper, $value)
{
if (null === self::$escapers) {
self::initializeEscapers();
}
$this->escaper = is_string($escaper) && isset(self::$escapers[$escaper]) ? self::$escapers[$escaper] : $escaper;
$this->value = $value;
}
/**
* Decorates a PHP variable with something that will escape any data obtained
* from it.
@ -78,7 +44,7 @@ abstract class Escaper
* The escaping method is actually a PHP callable. This class hosts a set
* of standard escaping strategies.
*
* @param string $escaper The escaping method (a PHP callable) to apply to the value
* @param mixed $escaper The escaping method (a PHP callable or a named escaper) to apply to the value
* @param mixed $value The value to escape
*
* @return mixed Escaped value
@ -109,11 +75,10 @@ abstract class Escaper
}
if (is_object($value)) {
if ($value instanceof Escaper) {
if ($value instanceof BaseEscaper) {
// avoid double decoration
$copy = clone $value;
$copy->escaper = $escaper;
$copy->setEscaper($escaper);
return $copy;
}
@ -121,7 +86,7 @@ abstract class Escaper
if ($value instanceof SafeDecorator) {
// do not escape objects marked as safe
// return the original object
return $value->getValue();
return $value->getRawValue();
}
if (self::isClassMarkedAsSafe(get_class($value)) || $value instanceof SafeDecoratorInterface) {
@ -170,7 +135,7 @@ abstract class Escaper
}
if (is_object($value)) {
return $value instanceof Escaper ? $value->getRawValue() : $value;
return $value instanceof BaseEscaper ? $value->getRawValue() : $value;
}
return $value;
@ -218,19 +183,6 @@ abstract class Escaper
self::markClassesAsSafe(array($class));
}
/**
* Returns the raw value associated with this instance.
*
* Concrete instances of Escaper classes decorate a value which is
* stored by the constructor. This returns that original, unescaped, value.
*
* @return mixed The original value used to construct the decorator
*/
public function getRawValue()
{
return $this->value;
}
/**
* Sets the current charset.
*
@ -262,6 +214,22 @@ abstract class Escaper
self::$escapers[$name] = $escaper;
}
/**
* Gets a named escaper.
*
* @param string $name The escaper name
*
* @return mixed $escaper A PHP callable
*/
static public function getEscaper($escaper)
{
if (null === self::$escapers) {
self::initializeEscapers();
}
return is_string($escaper) && isset(self::$escapers[$escaper]) ? self::$escapers[$escaper] : $escaper;
}
/**
* Initializes the built-in escapers.
*

View File

@ -1,54 +0,0 @@
<?php
namespace Symfony\Component\OutputEscaper;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract output escaping decorator class for "getter" objects.
*
* @see Escaper
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
abstract class GetterDecorator extends Escaper
{
/**
* Returns the raw, unescaped value associated with the key supplied.
*
* The key might be an index into an array or a value to be passed to the
* decorated object's get() method.
*
* @param string $key The key to retrieve
*
* @return mixed The value
*/
public abstract function getRaw($key);
/**
* Returns the escaped value associated with the key supplied.
*
* Typically (using this implementation) the raw value is obtained using the
* {@link getRaw()} method, escaped and the result returned.
*
* @param string $key The key to retrieve
* @param string $escaper The escaping method (a PHP function) to use
*
* @return mixed The escaped value
*/
public function get($key, $escaper = null)
{
if (!$escaper) {
$escaper = $this->escaper;
}
return Escaper::escape($escaper, $this->getRaw($key));
}
}

View File

@ -21,7 +21,7 @@ namespace Symfony\Component\OutputEscaper;
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
class IteratorDecorator extends ObjectDecorator implements \Iterator, \Countable, \ArrayAccess
class IteratorDecorator extends ObjectDecorator implements \Iterator
{
/**
* The iterator to be used.
@ -94,71 +94,4 @@ class IteratorDecorator extends ObjectDecorator implements \Iterator, \Countable
{
return $this->iterator->valid();
}
/**
* Returns true if the supplied offset isset in the array (as required by the ArrayAccess interface).
*
* @param string $offset The offset of the value to check existence of
*
* @return bool true if the offset isset; false otherwise
*/
public function offsetExists($offset)
{
return isset($this->value[$offset]);
}
/**
* Returns the element associated with the offset supplied (as required by the ArrayAccess interface).
*
* @param string $offset The offset of the value to get
*
* @return mixed The escaped value
*/
public function offsetGet($offset)
{
return Escaper::escape($this->escaper, $this->value[$offset]);
}
/**
* Throws an exception saying that values cannot be set (this method is
* required for the ArrayAccess interface).
*
* This (and the other Escaper classes) are designed to be read only
* so this is an illegal operation.
*
* @param string $offset (ignored)
* @param string $value (ignored)
*
* @throws \LogicException When trying to set values
*/
public function offsetSet($offset, $value)
{
throw new \LogicException('Cannot set values.');
}
/**
* Throws an exception saying that values cannot be unset (this method is
* required for the ArrayAccess interface).
*
* This (and the other Escaper classes) are designed to be read only
* so this is an illegal operation.
*
* @param string $offset (ignored)
*
* @throws \LogicException When trying to unset values
*/
public function offsetUnset($offset)
{
throw new \LogicException('Cannot unset values.');
}
/**
* Returns the size of the array (are required by the Countable interface).
*
* @return int The size of the array
*/
public function count()
{
return count($this->value);
}
}

View File

@ -19,7 +19,7 @@ namespace Symfony\Component\OutputEscaper;
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Mike Squire <mike@somosis.co.uk>
*/
class ObjectDecorator extends GetterDecorator
class ObjectDecorator extends BaseEscaper implements \ArrayAccess, \Countable
{
/**
* Magic PHP method that intercepts method calls, calls them on the objects
@ -62,27 +62,6 @@ class ObjectDecorator extends GetterDecorator
return Escaper::escape($escaper, $value);
}
/**
* Returns the result of calling the get() method on the object, bypassing
* any escaping, if that method exists.
*
* If there is not a callable get() method this will throw an exception.
*
* @param string $key The parameter to be passed to the get() get method
*
* @return mixed The unescaped value returned
*
* @throws \LogicException if the object does not have a callable get() method
*/
public function getRaw($key)
{
if (!is_callable(array($this->value, 'get'))) {
throw new \LogicException('Object does not have a callable get() method.');
}
return $this->value->get($key);
}
/**
* Try to call decorated object __toString() method if exists.
*
@ -90,7 +69,7 @@ class ObjectDecorator extends GetterDecorator
*/
public function __toString()
{
return $this->escape($this->escaper, (string) $this->value);
return Escaper::escape($this->escaper, (string) $this->value);
}
/**
@ -102,7 +81,7 @@ class ObjectDecorator extends GetterDecorator
*/
public function __get($key)
{
return $this->escape($this->escaper, $this->value->$key);
return Escaper::escape($this->escaper, $this->value->$key);
}
/**
@ -116,4 +95,97 @@ class ObjectDecorator extends GetterDecorator
{
return isset($this->value->$key);
}
/**
* Escapes an object property using the specified escaper.
*
* @param string $key The object property name
* @param mixed $escaper The escaping method (a PHP callable or a named escaper)
*/
public function getEscapedProperty($key, $escaper)
{
return Escaper::escape($escaper, $this->value->$key);
}
/**
* Returns true if the supplied offset isset in the array (as required by the ArrayAccess interface).
*
* @param string $offset The offset of the value to check existence of
*
* @return bool true if the offset isset; false otherwise
*/
public function offsetExists($offset)
{
return isset($this->value[$offset]);
}
/**
* Returns the element associated with the offset supplied (as required by the ArrayAccess interface).
*
* @param string $offset The offset of the value to get
*
* @return mixed The escaped value
*/
public function offsetGet($offset)
{
return Escaper::escape($this->escaper, $this->value[$offset]);
}
/**
* Throws an exception saying that values cannot be set (this method is
* required for the ArrayAccess interface).
*
* This (and the other Escaper classes) are designed to be read only
* so this is an illegal operation.
*
* @param string $offset (ignored)
* @param string $value (ignored)
*
* @throws \LogicException When trying to set values
*/
public function offsetSet($offset, $value)
{
throw new \LogicException('Cannot set values.');
}
/**
* Throws an exception saying that values cannot be unset (this method is
* required for the ArrayAccess interface).
*
* This (and the other Escaper classes) are designed to be read only
* so this is an illegal operation.
*
* @param string $offset (ignored)
*
* @throws \LogicException When trying to unset values
*/
public function offsetUnset($offset)
{
throw new \LogicException('Cannot unset values.');
}
/**
* Escapes a key from the array using the specified escaper.
*
* @param string $key The array key
* @param mixed $escaper The escaping method (a PHP callable or a named escaper)
*/
public function getEscapedKey($key, $escaper)
{
return Escaper::escape($escaper, $this->value[$key]);
}
/**
* Returns the size of the array (are required by the Countable interface).
*
* @return int The size of the array
*/
public function count()
{
if ($this->value instanceof \Countable) {
return count($this->value);
}
return call_user_func_array(array($this->value, 'count'), func_get_args());
}
}

View File

@ -16,7 +16,7 @@ namespace Symfony\Component\OutputEscaper;
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class SafeDecorator extends \ArrayIterator implements SafeDecoratorInterface
class SafeDecorator implements SafeDecoratorInterface
{
protected $value;
@ -28,48 +28,14 @@ class SafeDecorator extends \ArrayIterator implements SafeDecoratorInterface
public function __construct($value)
{
$this->value = $value;
if (is_array($value) || is_object($value)) {
parent::__construct($value);
}
}
public function __toString()
{
return (string) $this->value;
}
public function __get($key)
{
return $this->value->$key;
}
public function __set($key, $value)
{
$this->value->$key = $value;
}
public function __call($method, $arguments)
{
return call_user_func_array(array($this->value, $method), $arguments);
}
public function __isset($key)
{
return isset($this->value->$key);
}
public function __unset($key)
{
unset($this->value->$key);
}
/**
* Returns the embedded value.
* Returns the raw value.
*
* @return mixed The embedded value
* @return mixed The raw value
*/
public function getValue()
public function getRawValue()
{
return $this->value;
}

View File

@ -24,9 +24,9 @@ class ArrayDecoratorTest extends \PHPUnit_Framework_TestCase
self::$escaped = Escaper::escape('entities', $a);
}
public function testGetRaw()
public function testGetEscapedKey()
{
$this->assertEquals('<strong>escaped!</strong>', self::$escaped->getRaw(0), '->getRaw() returns the raw value');
$this->assertEquals('<strong>escaped!</strong>', self::$escaped->getEscapedKey(0, 'raw'), '->getEscapedKey() returns the value with an other escaper');
}
public function testArrayAccessInterface()

View File

@ -120,7 +120,7 @@ class EscaperTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('<strong>escaped!</strong>', $output->title, '::unescape() unescapes all properties of the original object');
$this->assertEquals('<strong>escaped!</strong>', $output->getTitleTitle(), '::unescape() is recursive');
$this->assertInstanceOf('\DirectoryIterator', IteratorDecorator::unescape(Escaper::escape('entities', new \DirectoryIterator('.'))), '::unescape() unescapes IteratorDecorator objects');
$this->assertInstanceOf('\DirectoryIterator', Escaper::unescape(Escaper::escape('entities', new \DirectoryIterator('.'))), '::unescape() unescapes IteratorDecorator objects');
}
public function testUnescapeDoesNotUnescapeObjectMarkedAsBeingSafe()

View File

@ -50,21 +50,6 @@ class ObjectDecoratorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(isset(self::$escaped->someMember), 'The escaped object behaves like the real object');
$this->assertFalse(isset(self::$escaped->invalidMember), 'The escaped object behaves like the real object');
}
public function testGetRaw()
{
$this->assertEquals('<em>escape me</em>', self::$escaped->getRaw('someMember'), '->getRaw() returns result with any escaping');
}
/**
* @expectedException LogicException
*/
public function testGetRawException()
{
$object = new \stdClass();
$escaped = Escaper::escape('entities', $object);
$escaped->getRaw('something');
}
}
class OutputEscaperTest

View File

@ -15,96 +15,9 @@ use Symfony\Component\OutputEscaper\SafeDecorator;
class SafeDecoratorTest extends \PHPUnit_Framework_TestCase
{
public function testGetValue()
public function testGetRawValue()
{
$safe = new SafeDecorator('foo');
$this->assertEquals('foo', $safe->getValue(), '->getValue() returns the embedded value');
}
public function testMagicGetAndSet()
{
$safe = new SafeDecorator(new TestClass1());
$this->assertEquals('bar', $safe->foo, '->__get() returns the object parameter');
$safe->foo = 'baz';
$this->assertEquals('baz', $safe->foo, '->__set() sets the object parameter');
}
public function testMagicCall()
{
$safe = new SafeDecorator(new TestClass2());
$this->assertEquals('ok', $safe->doSomething(), '->__call() invokes the embedded method');
}
public function testMagicToString()
{
$safe = new SafeDecorator(new TestClass4());
$this->assertEquals('TestClass4', (string)$safe, '->__toString() invokes the embedded __toString method');
}
public function testMagicIssetAndUnset()
{
$safe = new SafeDecorator(new TestClass3());
$this->assertTrue(isset($safe->boolValue), '->__isset() returns true if the property is not null');
$this->assertFalse(isset($safe->nullValue), '->__isset() returns false if the property is null');
$this->assertFalse(isset($safe->undefinedValue), '->__isset() returns false if the property does not exist');
unset($safe->boolValue);
$this->assertFalse(isset($safe->boolValue), '->__unset() unsets the embedded property');
}
public function testIteratorInterface()
{
$input = array('one' => 1, 'two' => 2, 'three' => 3, 'children' => array(1, 2, 3));
$output = array();
$safe = new SafeDecorator($input);
foreach ($safe as $key => $value) {
$output[$key] = $value;
}
$this->assertSame($output, $input, '"Iterator" implementation imitates an array');
}
public function testArrayAccessIterator()
{
$safe = new SafeDecorator(array('foo' => 'bar'));
$this->assertEquals('bar', $safe['foo'], '"ArrayAccess" implementation returns a value from the embedded array');
$safe['foo'] = 'baz';
$this->assertEquals('baz', $safe['foo'], '"ArrayAccess" implementation sets a value on the embedded array');
$this->assertTrue(isset($safe['foo']), '"ArrayAccess" checks if a value is set on the embedded array');
unset($safe['foo']);
$this->assertFalse(isset($safe['foo']), '"ArrayAccess" unsets a value on the embedded array');
$this->assertEquals('foo', $safe->getRawValue(), '->getValue() returns the embedded value');
}
}
class TestClass1
{
public $foo = 'bar';
}
class TestClass2
{
public function doSomething()
{
return 'ok';
}
}
class TestClass3
{
public
$boolValue = true,
$nullValue = null;
}
class TestClass4
{
public function __toString()
{
return 'TestClass4';
}
}