diff --git a/src/AbstractOptions.php b/src/AbstractOptions.php index beff2f6db..8099c75ff 100644 --- a/src/AbstractOptions.php +++ b/src/AbstractOptions.php @@ -5,17 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; use Traversable; -/** - * @category Zend - * @package Zend_Stdlib - */ abstract class AbstractOptions implements ParameterObjectInterface { /** @@ -28,8 +23,6 @@ abstract class AbstractOptions implements ParameterObjectInterface /** * @param array|Traversable|null $options - * @return AbstractOptions - * @throws Exception\InvalidArgumentException */ public function __construct($options = null) { @@ -41,7 +34,7 @@ public function __construct($options = null) /** * @param array|Traversable $options * @throws Exception\InvalidArgumentException - * @return void + * @return AbstractOptions Provides fluent interface */ public function setFromArray($options) { @@ -55,6 +48,7 @@ public function setFromArray($options) foreach ($options as $key => $value) { $this->__set($key, $value); } + return $this; } /** @@ -115,6 +109,7 @@ public function __get($key) . 'which must be defined' ); } + return $this->{$getter}(); } @@ -131,14 +126,14 @@ public function __isset($key) /** * @see ParameterObject::__unset() * @param string $key - * @return void * @throws Exception\InvalidArgumentException + * @return void */ public function __unset($key) { try { $this->__set($key, null); - } catch (\InvalidArgumentException $e) { + } catch (Exception\BadMethodCallException $e) { throw new Exception\InvalidArgumentException( 'The class property $' . $key . ' cannot be unset as' . ' NULL is an invalid value for it', diff --git a/src/ArraySerializableInterface.php b/src/ArraySerializableInterface.php index a179f4e42..7bd6f6edd 100644 --- a/src/ArraySerializableInterface.php +++ b/src/ArraySerializableInterface.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ interface ArraySerializableInterface { /** diff --git a/src/ArrayStack.php b/src/ArrayStack.php index f94f4522b..50eaad6e6 100644 --- a/src/ArrayStack.php +++ b/src/ArrayStack.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -15,9 +14,6 @@ /** * ArrayObject that acts as a stack with regards to iteration - * - * @category Zend - * @package Zend_Stdlib */ class ArrayStack extends ArrayObject { diff --git a/src/ArrayUtils.php b/src/ArrayUtils.php index b2870efe0..b073f3b45 100644 --- a/src/ArrayUtils.php +++ b/src/ArrayUtils.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -16,9 +15,6 @@ * Utility class for testing and manipulation of PHP arrays. * * Declared abstract, as we have no need for instantiation. - * - * @category Zend - * @package Zend_Stdlib */ abstract class ArrayUtils { @@ -164,6 +160,36 @@ public static function isHashTable($value, $allowEmpty = false) return (array_values($value) !== $value); } + /** + * Checks if a value exists in an array. + * + * Due to "foo" == 0 === TRUE with in_array when strict = false, an option + * has been added to prevent this. When $strict = 0/false, the most secure + * non-strict check is implemented. if $strict = -1, the default in_array + * non-strict behaviour is used. + * + * @param mixed $needle + * @param array $haystack + * @param int|bool $strict + * @return bool + */ + public static function inArray($needle, array $haystack, $strict = false) + { + if (!$strict) { + if (is_int($needle) || is_float($needle)) { + $needle = (string) $needle; + } + if (is_string($needle)) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + } + } + return in_array($needle, $haystack, $strict); + } + /** * Convert an iterator to an array. * diff --git a/src/CallbackHandler.php b/src/CallbackHandler.php index 22effba7d..bd60a6756 100644 --- a/src/CallbackHandler.php +++ b/src/CallbackHandler.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -20,9 +19,6 @@ * A handler for a event, event, filterchain, etc. Abstracts PHP callbacks, * primarily to allow for lazy-loading and ensuring availability of default * arguments (currying). - * - * @category Zend - * @package Zend_Stdlib */ class CallbackHandler { diff --git a/src/DateTime.php b/src/DateTime.php index 032fc94ff..308a25b53 100644 --- a/src/DateTime.php +++ b/src/DateTime.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -16,9 +15,6 @@ * DateTime * * An extension of the \DateTime object. - * - * @category Zend - * @package Zend_Stdlib */ class DateTime extends \DateTime { @@ -44,5 +40,4 @@ public static function createFromISO8601($time, DateTimeZone $timezone = null) return self::createFromFormat($format, $time); } - } diff --git a/src/DispatchableInterface.php b/src/DispatchableInterface.php index 2e6fa95f9..ab672fa13 100644 --- a/src/DispatchableInterface.php +++ b/src/DispatchableInterface.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -13,10 +12,6 @@ use Zend\Stdlib\RequestInterface as Request; use Zend\Stdlib\ResponseInterface as Response; -/** - * @category Zend - * @package Zend_Stdlib - */ interface DispatchableInterface { /** diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 05204831e..92f399575 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -15,53 +14,48 @@ /** * ErrorHandler that can be used to catch internal PHP errors * and convert to a ErrorException instance. - * - * @category Zend - * @package Zend_Stdlib */ abstract class ErrorHandler { /** - * Flag to mark started + * Active stack * - * @var bool + * @var array */ - protected static $started = false; + protected static $stack = array(); /** - * All errors as one instance of ErrorException - * using the previous exception support. + * Check if this error handler is active * - * @var null|ErrorException + * @return boolean */ - protected static $errorException = null; + public static function started() + { + return (bool) static::getNestedLevel(); + } /** - * If the error handler has been started. + * Get the current nested level * - * @return bool + * @return int */ - public static function started() + public static function getNestedLevel() { - return static::$started; + return count(static::$stack); } /** * Starting the error handler * * @param int $errorLevel - * @throws Exception\LogicException If already started */ public static function start($errorLevel = \E_WARNING) { - if (static::started() === true) { - throw new Exception\LogicException('ErrorHandler already started'); + if (!static::$stack) { + set_error_handler(array(get_called_class(), 'addError'), $errorLevel); } - static::$started = true; - static::$errorException = null; - - set_error_handler(array(get_called_class(), 'addError'), $errorLevel); + static::$stack[] = null; } /** @@ -69,30 +63,43 @@ public static function start($errorLevel = \E_WARNING) * * @param bool $throw Throw the ErrorException if any * @return null|ErrorException - * @throws Exception\LogicException If not started before * @throws ErrorException If an error has been catched and $throw is true */ public static function stop($throw = false) { - if (static::started() === false) { - throw new Exception\LogicException('ErrorHandler not started'); - } + $errorException = null; - $errorException = static::$errorException; + if (static::$stack) { + $errorException = array_pop(static::$stack); - static::$started = false; - static::$errorException = null; - restore_error_handler(); + if (!static::$stack) { + restore_error_handler(); + } - if ($errorException && $throw) { - throw $errorException; + if ($errorException && $throw) { + throw $errorException; + } } return $errorException; } /** - * Add an error to the stack. + * Stop all active handler + * + * @return void + */ + public static function clean() + { + if (static::$stack) { + restore_error_handler(); + } + + static::$stack = array(); + } + + /** + * Add an error to the stack * * @param int $errno * @param string $errstr @@ -102,6 +109,7 @@ public static function stop($throw = false) */ public static function addError($errno, $errstr = '', $errfile = '', $errline = 0) { - static::$errorException = new ErrorException($errstr, 0, $errno, $errfile, $errline, static::$errorException); + $stack = & static::$stack[ count(static::$stack) - 1 ]; + $stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack); } } diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php index 079dbfe59..6cf1c9ecc 100644 --- a/src/Exception/BadMethodCallException.php +++ b/src/Exception/BadMethodCallException.php @@ -5,17 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Exception; /** * Bad method call exception - * - * @category Zend - * @package Zend_Stdlib - * @subpackage Exception */ class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface { diff --git a/src/Exception/DomainException.php b/src/Exception/DomainException.php index cf6b43b16..2744570f2 100644 --- a/src/Exception/DomainException.php +++ b/src/Exception/DomainException.php @@ -5,17 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Exception; /** * Domain exception - * - * @category Zend - * @package Zend_Stdlib - * @subpackage Exception */ class DomainException extends \DomainException implements ExceptionInterface { diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index c3013a0f0..0424a4ea9 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -5,17 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Exception; /** * Exception marker interface - * - * @category Zend - * @package Zend_Stdlib - * @subpackage Exception */ interface ExceptionInterface { diff --git a/src/Exception/ExtensionNotLoadedException.php b/src/Exception/ExtensionNotLoadedException.php new file mode 100644 index 000000000..b883641a6 --- /dev/null +++ b/src/Exception/ExtensionNotLoadedException.php @@ -0,0 +1,17 @@ +strategies = new ArrayObject(); + $this->filterComposite = new FilterComposite(); } /** @@ -44,7 +47,19 @@ public function __construct() */ public function getStrategy($name) { - return $this->strategies[$name]; + if (isset($this->strategies[$name])) { + return $this->strategies[$name]; + } + + if (!isset($this->strategies['*'])) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: no strategy by name of "%s", and no wildcard strategy present', + __METHOD__, + $name + )); + } + + return $this->strategies['*']; } /** @@ -55,7 +70,8 @@ public function getStrategy($name) */ public function hasStrategy($name) { - return array_key_exists($name, $this->strategies); + return array_key_exists($name, $this->strategies) + || array_key_exists('*', $this->strategies); } /** @@ -114,4 +130,67 @@ public function hydrateValue($name, $value) } return $value; } + + /** + * Get the filter instance + * + * @return Filter\FilterComposite + */ + public function getFilter() + { + return $this->filterComposite; + } + + /** + * Add a new filter to take care of what needs to be hydrated. + * To exclude e.g. the method getServiceLocator: + * + * + * $composite->addFilter("servicelocator", + * function($property) { + * list($class, $method) = explode('::', $property); + * if ($method === 'getServiceLocator') { + * return false; + * } + * return true; + * }, FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name Index in the composite + * @param callable|Zend\Stdlib\Hydrator\Filter\FilterInterface $filter + * @param int $condition + * @return Filter\FilterComposite + */ + public function addFilter($name, $filter, $condition = FilterComposite::CONDITION_OR) + { + return $this->filterComposite->addFilter($name, $filter, $condition); + } + + /** + * Check whether a specific filter exists at key $name or not + * + * @param string $name Index in the composite + * @return bool + */ + public function hasFilter($name) + { + return $this->filterComposite->hasFilter($name); + } + + /** + * Remove a filter from the composition. + * To not extract "has" methods, you simply need to unregister it + * + * + * $filterComposite->removeFilter('has'); + * + * + * @param $name + * @return Filter\FilterComposite + */ + public function removeFilter($name) + { + return $this->filterComposite->removeFilter($name); + } } diff --git a/src/Hydrator/ArraySerializable.php b/src/Hydrator/ArraySerializable.php index fbe214db9..09dfebba1 100644 --- a/src/Hydrator/ArraySerializable.php +++ b/src/Hydrator/ArraySerializable.php @@ -5,18 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator; use Zend\Stdlib\Exception; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ class ArraySerializable extends AbstractHydrator { @@ -39,9 +33,14 @@ public function extract($object) $self = $this; $data = $object->getArrayCopy(); - array_walk($data, function (&$value, $name) use ($self) { - $value = $self->extractValue($name, $value); + array_walk($data, function (&$value, $name) use ($self, &$data) { + if (!$self->getFilter()->filter($name)) { + unset($data[$name]); + } else { + $value = $self->extractValue($name, $value); + } }); + return $data; } diff --git a/src/Hydrator/ClassMethods.php b/src/Hydrator/ClassMethods.php index 06b2336fa..d29185fe6 100644 --- a/src/Hydrator/ClassMethods.php +++ b/src/Hydrator/ClassMethods.php @@ -5,34 +5,83 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator; +use ReflectionMethod; +use Traversable; use Zend\Stdlib\Exception; +use Zend\Stdlib\ArrayUtils; +use Zend\Stdlib\Hydrator\Filter\FilterComposite; +use Zend\Stdlib\Hydrator\Filter\FilterProviderInterface; +use Zend\Stdlib\Hydrator\Filter\MethodMatchFilter; +use Zend\Stdlib\Hydrator\Filter\GetFilter; +use Zend\Stdlib\Hydrator\Filter\HasFilter; +use Zend\Stdlib\Hydrator\Filter\IsFilter; +use Zend\Stdlib\Hydrator\Filter\NumberOfParameterFilter; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ -class ClassMethods extends AbstractHydrator +class ClassMethods extends AbstractHydrator implements HydratorOptionsInterface { /** * Flag defining whether array keys are underscore-separated (true) or camel case (false) * @var bool */ - protected $underscoreSeparatedKeys; + protected $underscoreSeparatedKeys = true; /** * Define if extract values will use camel case or name with underscore - * @param bool $underscoreSeparatedKeys + * @param bool|array $underscoreSeparatedKeys */ public function __construct($underscoreSeparatedKeys = true) { parent::__construct(); + $this->setUnderscoreSeparatedKeys($underscoreSeparatedKeys); + + $this->filterComposite->addFilter("is", new IsFilter()); + $this->filterComposite->addFilter("has", new HasFilter()); + $this->filterComposite->addFilter("get", new GetFilter()); + $this->filterComposite->addFilter("parameter", new NumberOfParameterFilter(), FilterComposite::CONDITION_AND); + } + + /** + * @param array|\Traversable $options + * @return ClassMethods + * @throws Exception\InvalidArgumentException + */ + public function setOptions($options) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (!is_array($options)) { + throw new Exception\InvalidArgumentException( + 'The options parameter must be an array or a Traversable' + ); + } + if (isset($options['underscoreSeparatedKeys'])) { + $this->setUnderscoreSeparatedKeys($options['underscoreSeparatedKeys']); + } + + return $this; + } + + /** + * @param boolean $underscoreSeparatedKeys + * @return ClassMethods + */ + public function setUnderscoreSeparatedKeys($underscoreSeparatedKeys) + { $this->underscoreSeparatedKeys = $underscoreSeparatedKeys; + + return $this; + } + + /** + * @return boolean + */ + public function getUnderscoreSeparatedKeys() + { + return $this->underscoreSeparatedKeys; } /** @@ -40,7 +89,7 @@ public function __construct($underscoreSeparatedKeys = true) * * Extracts the getter/setter of the given $object. * - * @param object $object + * @param object $object * @return array * @throws Exception\BadMethodCallException for a non-object $object */ @@ -52,15 +101,35 @@ public function extract($object) )); } + $filter = null; + if ($object instanceof FilterProviderInterface) { + $filter = new FilterComposite( + array($object->getFilter()), + array(new MethodMatchFilter("getFilter")) + ); + } else { + $filter = $this->filterComposite; + } + $transform = function ($letters) { $letter = array_shift($letters); + return '_' . strtolower($letter); }; $attributes = array(); $methods = get_class_methods($object); foreach ($methods as $method) { - if (!preg_match('/^(get|has|is)[A-Z]\w*/', $method)) { + if ( + !$filter->filter( + get_class($object) . '::' . $method + ) + ) { + continue; + } + + $reflectionMethod = new ReflectionMethod(get_class($object) . '::' . $method); + if ($reflectionMethod->getNumberOfParameters() > 0) { continue; } @@ -84,8 +153,8 @@ public function extract($object) * * Hydrates an object by getter/setter methods of the object. * - * @param array $data - * @param object $object + * @param array $data + * @param object $object * @return object * @throws Exception\BadMethodCallException for a non-object $object */ @@ -99,6 +168,7 @@ public function hydrate(array $data, $object) $transform = function ($letters) { $letter = substr(array_shift($letters), 1, 1); + return ucfirst($letter); }; @@ -113,6 +183,8 @@ public function hydrate(array $data, $object) $object->$method($value); } } + return $object; } + } diff --git a/src/Hydrator/Filter/FilterComposite.php b/src/Hydrator/Filter/FilterComposite.php new file mode 100644 index 000000000..1c8dc63ed --- /dev/null +++ b/src/Hydrator/Filter/FilterComposite.php @@ -0,0 +1,198 @@ +orFilter = new ArrayObject($orFilter); + $this->andFilter = new ArrayObject($andFilter); + } + + /** + * Add a filter to the composite. Has to be indexed with $name in + * order to identify a specific filter. + * + * This example will exclude all methods from the hydration, that starts with 'getService' + * + * $composite->addFilter('exclude', + * function($method) { + * if (preg_match('/^getService/', $method) { + * return false; + * } + * return true; + * }, FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name + * @param callable|FilterInterface $filter + * @param int $condition Can be either FilterComposite::CONDITION_OR or FilterComposite::CONDITION_AND + * @throws InvalidArgumentException + * @return FilterComposite + */ + public function addFilter($name, $filter, $condition = self::CONDITION_OR) + { + if ( !is_callable($filter) && !($filter instanceof FilterInterface) ) { + throw new InvalidArgumentException( + 'The value of ' . $name . ' should be either a callable or ' . + 'an instance of Zend\Stdlib\Hydrator\Filter\FilterInterface' + ); + } + + if ($condition === self::CONDITION_OR) { + $this->orFilter[$name] = $filter; + } elseif ($condition === self::CONDITION_AND) { + $this->andFilter[$name] = $filter; + } + + return $this; + } + + /** + * Remove a filter from the composition + * + * @param $name string Identifier for the filter + * @return FilterComposite + */ + public function removeFilter($name) + { + if (isset($this->orFilter[$name])) { + unset($this->orFilter[$name]); + } + + if (isset($this->andFilter[$name])) { + unset($this->andFilter[$name]); + } + + return $this; + } + + /** + * Check if $name has a filter registered + * + * @param $name string Identifier for the filter + * @return bool + */ + public function hasFilter($name) + { + return isset($this->orFilter[$name]) || isset($this->andFilter[$name]); + } + + /** + * Filter the composite based on the AND and OR condition + * Will return true if one from the "or conditions" and all from + * the "and condition" returns true. Otherwise false + * + * @param $property string Parameter will be e.g. Parent\Namespace\Class::method + * @return bool + */ + public function filter($property) + { + $andCount = count($this->andFilter); + $orCount = count($this->orFilter); + // return true if no filters are registered + if ($orCount === 0 && $andCount === 0) { + return true; + } elseif ($orCount === 0 && $andCount !== 0) { + $returnValue = true; + } else { + $returnValue = false; + } + + // Check if 1 from the or filters return true + foreach ($this->orFilter as $filter) { + if (is_callable($filter)) { + if ( $filter($property) === true) { + $returnValue = true; + break; + } + continue; + } else { + if ( $filter->filter($property) === true) { + $returnValue = true; + break; + } + } + } + + // Check if all of the and condition return true + foreach ($this->andFilter as $filter) { + if (is_callable($filter)) { + if ($filter($property) === false) { + return false; + } + continue; + } else { + if ($filter->filter($property) === false) { + return false; + } + } + } + + return $returnValue; + } +} diff --git a/src/Hydrator/Filter/FilterInterface.php b/src/Hydrator/Filter/FilterInterface.php new file mode 100644 index 000000000..56cb4ecf6 --- /dev/null +++ b/src/Hydrator/Filter/FilterInterface.php @@ -0,0 +1,21 @@ +method = $method; + $this->exclude = $exclude; + } + + public function filter($property) + { + $pos = strpos($property, '::'); + if ($pos !== false) { + $pos += 2; + } else { + $pos = 0; + } + if (substr($property, $pos) === $this->method) { + return $this->exclude ? false : true; + } + return $this->exclude ? true : false; + } +} diff --git a/src/Hydrator/Filter/NumberOfParameterFilter.php b/src/Hydrator/Filter/NumberOfParameterFilter.php new file mode 100644 index 000000000..df6e123f4 --- /dev/null +++ b/src/Hydrator/Filter/NumberOfParameterFilter.php @@ -0,0 +1,53 @@ +numberOfParameters = 0; + } + + /** + * @param string $property the name of the property + * @throws InvalidArgumentException + */ + public function filter($property) + { + try { + $reflectionMethod = new ReflectionMethod($property); + } catch( ReflectionException $exception) { + throw new InvalidArgumentException( + "Method $property doesn't exist" + ); + } + + if ($reflectionMethod->getNumberOfParameters() !== $this->numberOfParameters) { + return false; + } + + return true; + } +} diff --git a/src/Hydrator/HydratorInterface.php b/src/Hydrator/HydratorInterface.php index 0fb1c965b..c6b3e89e2 100644 --- a/src/Hydrator/HydratorInterface.php +++ b/src/Hydrator/HydratorInterface.php @@ -5,16 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ interface HydratorInterface { /** diff --git a/src/Hydrator/HydratorOptionsInterface.php b/src/Hydrator/HydratorOptionsInterface.php new file mode 100644 index 000000000..deda37155 --- /dev/null +++ b/src/Hydrator/HydratorOptionsInterface.php @@ -0,0 +1,19 @@ +extractValue($name, $value); + array_walk($data, function (&$value, $name) use ($self, &$data) { + if (!$self->getFilter()->filter($name)) { + unset($data[$name]); + } else { + $value = $self->extractValue($name, $value); + } }); return $data; } diff --git a/src/Hydrator/Reflection.php b/src/Hydrator/Reflection.php index b372665d1..6fe230204 100644 --- a/src/Hydrator/Reflection.php +++ b/src/Hydrator/Reflection.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator; @@ -13,11 +12,6 @@ use ReflectionClass; use Zend\Stdlib\Exception; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ class Reflection extends AbstractHydrator { /** @@ -37,6 +31,9 @@ public function extract($object) $result = array(); foreach (self::getReflProperties($object) as $property) { $propertyName = $property->getName(); + if (!$this->filterComposite->filter($propertyName)) { + continue; + } $value = $property->getValue($object); $result[$propertyName] = $this->extractValue($propertyName, $value); diff --git a/src/Hydrator/Strategy/ClosureStrategy.php b/src/Hydrator/Strategy/ClosureStrategy.php new file mode 100644 index 000000000..1367b5c06 --- /dev/null +++ b/src/Hydrator/Strategy/ClosureStrategy.php @@ -0,0 +1,100 @@ +addStrategy('category', new ClosureStrategy( + * function(Category $value) { + * return (int)$value->id; + * }, + * function($value) { + * return new Category((int)$value); + * } + * )); + * + * @param callable $extractFunc - anonymous function, that extract values + * from object + * @param callable $hydrateFunc - anonymous function, that hydrate values + * into object + */ + public function __construct($extractFunc = null, $hydrateFunc = null) + { + if (isset($extractFunc)) { + if (!is_callable($extractFunc)) { + throw new \Exception('$extractFunc must be callable'); + } + + $this->extractFunc = $extractFunc; + } else { + $this->extractFunc = function($value) { + return $value; + }; + } + + if (isset($hydrateFunc)) { + if (!is_callable($hydrateFunc)) { + throw new \Exception('$hydrateFunc must be callable'); + } + + $this->hydrateFunc = $hydrateFunc; + } else { + $this->hydrateFunc = function($value) { + return $value; + }; + } + } + + /** + * Converts the given value so that it can be extracted by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be extracted. + */ + public function extract($value) + { + $func = $this->extractFunc; + + return $func($value); + } + + /** + * Converts the given value so that it can be hydrated by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be hydrated. + */ + public function hydrate($value) + { + $func = $this->hydrateFunc; + + return $func($value); + } +} diff --git a/src/Hydrator/Strategy/DefaultStrategy.php b/src/Hydrator/Strategy/DefaultStrategy.php index 1bd92f624..d98f1f4c7 100644 --- a/src/Hydrator/Strategy/DefaultStrategy.php +++ b/src/Hydrator/Strategy/DefaultStrategy.php @@ -5,16 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator\Strategy; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ class DefaultStrategy implements StrategyInterface { /** diff --git a/src/Hydrator/Strategy/SerializableStrategy.php b/src/Hydrator/Strategy/SerializableStrategy.php new file mode 100644 index 000000000..f286a8611 --- /dev/null +++ b/src/Hydrator/Strategy/SerializableStrategy.php @@ -0,0 +1,122 @@ +setSerializer($serializer); + if($serializerOptions) { + $this->setSerializerOptions($serializerOptions); + } + } + + /** + * Serialize the given value so that it can be extracted by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be extracted. + */ + public function extract($value) + { + $serializer = $this->getSerializer(); + return $serializer->serialize($value); + } + + /** + * Unserialize the given value so that it can be hydrated by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be hydrated. + */ + public function hydrate($value) + { + $serializer = $this->getSerializer(); + return $serializer->unserialize($value); + } + + /** + * Set serializer + * + * @param string|SerializerAdapter $serializer + * @return Serializer + */ + public function setSerializer($serializer) + { + if (!is_string($serializer) && !$serializer instanceof SerializerAdapter) { + throw new InvalidArgumentException(sprintf( + '%s expects either a string serializer name or Zend\Serializer\Adapter\AdapterInterface instance; ' + . 'received "%s"', + __METHOD__, + (is_object($serializer) ? get_class($serializer) : gettype($serializer)) + )); + } + $this->serializer = $serializer; + return $this; + } + + /** + * Get serializer + * + * @return SerializerAdapter + */ + public function getSerializer() + { + if (is_string($this->serializer)) { + $options = $this->getSerializerOptions(); + $this->setSerializer(SerializerFactory::factory($this->serializer, $options)); + } elseif (null === $this->serializer) { + $this->setSerializer(SerializerFactory::getDefaultAdapter()); + } + + return $this->serializer; + } + + /** + * Set configuration options for instantiating a serializer adapter + * + * @param mixed $serializerOptions + * @return SerializableStrategy + */ + public function setSerializerOptions($serializerOptions) + { + $this->serializerOptions = $serializerOptions; + return $this; + } + + /** + * Get configuration options for instantiating a serializer adapter + * + * @return mixed + */ + public function getSerializerOptions() + { + return $this->serializerOptions; + } +} diff --git a/src/Hydrator/Strategy/StrategyInterface.php b/src/Hydrator/Strategy/StrategyInterface.php index 83a27549d..85c21cc3f 100644 --- a/src/Hydrator/Strategy/StrategyInterface.php +++ b/src/Hydrator/Strategy/StrategyInterface.php @@ -5,16 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator\Strategy; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ interface StrategyInterface { /** diff --git a/src/Hydrator/StrategyEnabledInterface.php b/src/Hydrator/StrategyEnabledInterface.php index 721f58e7c..5cbb5580a 100644 --- a/src/Hydrator/StrategyEnabledInterface.php +++ b/src/Hydrator/StrategyEnabledInterface.php @@ -5,18 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib\Hydrator; use Zend\Stdlib\Hydrator\Strategy\StrategyInterface; -/** - * @category Zend - * @package Zend_Stdlib - * @subpackage Hydrator - */ interface StrategyEnabledInterface { /** diff --git a/src/InitializableInterface.php b/src/InitializableInterface.php new file mode 100644 index 000000000..b0bd2605f --- /dev/null +++ b/src/InitializableInterface.php @@ -0,0 +1,23 @@ +getContent(); return $request; } - } diff --git a/src/MessageInterface.php b/src/MessageInterface.php index e5b735ab4..0abb1ff6b 100644 --- a/src/MessageInterface.php +++ b/src/MessageInterface.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ interface MessageInterface { /** diff --git a/src/ParameterObjectInterface.php b/src/ParameterObjectInterface.php index ca724a464..416b8c298 100644 --- a/src/ParameterObjectInterface.php +++ b/src/ParameterObjectInterface.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ interface ParameterObjectInterface { /** diff --git a/src/Parameters.php b/src/Parameters.php index 895ca051e..421f49649 100644 --- a/src/Parameters.php +++ b/src/Parameters.php @@ -5,17 +5,12 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; use ArrayObject; -/** - * @category Zend - * @package Zend_Stdlib - */ class Parameters extends ArrayObject implements ParametersInterface { /** diff --git a/src/ParametersInterface.php b/src/ParametersInterface.php index fb70410ca..e955b2ac7 100644 --- a/src/ParametersInterface.php +++ b/src/ParametersInterface.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -19,9 +18,6 @@ * Basically, an ArrayObject. You could simply define something like: * class QueryParams extends ArrayObject implements Parameters {} * and have 90% of the functionality - * - * @category Zend - * @package Zend_Stdlib */ interface ParametersInterface extends ArrayAccess, Countable, Serializable, Traversable { diff --git a/src/PriorityQueue.php b/src/PriorityQueue.php index cf4ebef66..5c0c13db9 100644 --- a/src/PriorityQueue.php +++ b/src/PriorityQueue.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -25,9 +24,6 @@ * This class aggregates items for the queue itself, but also composes an * "inner" iterator in the form of an SplPriorityQueue object for performing * the actual iteration. - * - * @category Zend - * @package Zend_Stdlib */ class PriorityQueue implements Countable, IteratorAggregate, Serializable { diff --git a/src/Request.php b/src/Request.php index 60c3752fa..8427bc08f 100644 --- a/src/Request.php +++ b/src/Request.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ class Request extends Message implements RequestInterface { // generic request implementation diff --git a/src/RequestInterface.php b/src/RequestInterface.php index b858d6dd0..4a2252de4 100644 --- a/src/RequestInterface.php +++ b/src/RequestInterface.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ interface RequestInterface extends MessageInterface { } diff --git a/src/Response.php b/src/Response.php index 035155ea5..1c2ea76b1 100644 --- a/src/Response.php +++ b/src/Response.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ class Response extends Message implements ResponseInterface { // generic response implementation diff --git a/src/ResponseInterface.php b/src/ResponseInterface.php index ea6b88b48..cf5d0edd4 100644 --- a/src/ResponseInterface.php +++ b/src/ResponseInterface.php @@ -5,15 +5,10 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; -/** - * @category Zend - * @package Zend_Stdlib - */ interface ResponseInterface extends MessageInterface { diff --git a/src/SplPriorityQueue.php b/src/SplPriorityQueue.php index 20ced9cb2..5baa967ff 100644 --- a/src/SplPriorityQueue.php +++ b/src/SplPriorityQueue.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -17,9 +16,6 @@ * * Also, provides predictable heap order for datums added with the same priority * (i.e., they will be emitted in the same order they are enqueued). - * - * @category Zend - * @package Zend_Stdlib */ class SplPriorityQueue extends \SplPriorityQueue implements Serializable { diff --git a/src/SplQueue.php b/src/SplQueue.php index 1291e5f26..e18ebc682 100644 --- a/src/SplQueue.php +++ b/src/SplQueue.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -14,9 +13,6 @@ /** * Serializable version of SplQueue - * - * @category Zend - * @package Zend_Stdlib */ class SplQueue extends \SplQueue implements Serializable { diff --git a/src/SplStack.php b/src/SplStack.php index e739cebfd..3bb8f6796 100644 --- a/src/SplStack.php +++ b/src/SplStack.php @@ -5,7 +5,6 @@ * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License - * @package Zend_Stdlib */ namespace Zend\Stdlib; @@ -14,9 +13,6 @@ /** * Serializable version of SplStack - * - * @category Zend - * @package Zend_Stdlib */ class SplStack extends \SplStack implements Serializable { diff --git a/src/StringUtils.php b/src/StringUtils.php new file mode 100644 index 000000000..ce463131f --- /dev/null +++ b/src/StringUtils.php @@ -0,0 +1,189 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } + } + + throw new Exception\RuntimeException( + 'No wrapper found supporting "' . $encoding . '"' + . (($convertEncoding !== null) ? ' and "' . $convertEncoding . '"' : '') + ); + } + + /** + * Get a list of all known single-byte character encodings + * + * @return string[] + */ + public static function getSingleByteEncodings() + { + return static::$singleByteEncodings; + } + + /** + * Check if a given encoding is a known single-byte character encoding + * + * @param string $encoding + * @return boolean + */ + public static function isSingleByteEncoding($encoding) + { + return in_array(strtoupper($encoding), static::$singleByteEncodings); + } + + /** + * Check if a given string is valid UTF-8 encoded + * + * @param string $str + * @return boolean + */ + public static function isValidUtf8($str) + { + return is_string($str) && ($str === '' || preg_match('/^./su', $str) == 1); + } + + /** + * Is PCRE compiled with Unicode support? + * + * @return bool + */ + public static function hasPcreUnicodeSupport() + { + if (static::$hasPcreUnicodeSupport === null) { + ErrorHandler::start(); + static::$hasPcreUnicodeSupport = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1; + ErrorHandler::stop(); + } + return static::$hasPcreUnicodeSupport; + } +} diff --git a/src/StringWrapper/AbstractStringWrapper.php b/src/StringWrapper/AbstractStringWrapper.php new file mode 100644 index 000000000..948b77513 --- /dev/null +++ b/src/StringWrapper/AbstractStringWrapper.php @@ -0,0 +1,271 @@ +convertEncoding = $convertEncodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Get the defined character encoding to work with + * + * @return string + * @throws Exception\LogicException If no encoding was defined + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Get the defined character encoding to convert to + * + * @return string|null + */ + public function getConvertEncoding() + { + return $this->convertEncoding; + } + + /** + * Convert a string from defined character encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $from = $reverse ? $convertEncoding : $encoding; + $to = $reverse ? $encoding : $convertEncoding; + throw new Exception\RuntimeException(sprintf( + 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', + $from, + $to + )); + } + + /** + * Wraps a string to a given number of characters + * + * @param string $str + * @param integer $width + * @param string $break + * @param boolean $cut + * @return string|false + */ + public function wordWrap($string, $width = 75, $break = "\n", $cut = false) + { + $string = (string) $string; + if ($string === '') { + return ''; + } + + $break = (string) $break; + if ($break === '') { + throw new Exception\InvalidArgumentException('Break string cannot be empty'); + } + + $width = (int) $width; + if ($width === 0 && $cut) { + throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); + } + + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return wordwrap($string, $width, $break, $cut); + } + + $stringWidth = $this->strlen($string); + $breakWidth = $this->strlen($break); + + $result = ''; + $lastStart = $lastSpace = 0; + + for ($current = 0; $current < $stringWidth; $current++) { + $char = $this->substr($string, $current, 1); + + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = $this->substr($string, $current, $breakWidth); + } + + if ($possibleBreak === $break) { + $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + + $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace = $lastSpace + 1; + continue; + } + } + + if ($lastStart !== $current) { + $result .= $this->substr($string, $lastStart, $current - $lastStart); + } + + return $result; + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param integer $padLength + * @param string $padString + * @param integer $padType + * @return string + */ + public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + { + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return str_pad($input, $padLength, $padString, $padType); + } + + $lengthOfPadding = $padLength - $this->strlen($input); + if ($lengthOfPadding <= 0) { + return $input; + } + + $padStringLength = $this->strlen($padString); + if ($padStringLength === 0) { + return $input; + } + + $repeatCount = floor($lengthOfPadding / $padStringLength); + + if ($padType === STR_PAD_BOTH) { + $lastStringLeft = ''; + $lastStringRight = ''; + $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; + + $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; + $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); + $lastStringRightLength += $lastStringLength % 2; + + $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); + $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); + + return str_repeat($padString, $repeatCountLeft) . $lastStringLeft + . $input + . str_repeat($padString, $repeatCountRight) . $lastStringRight; + } + + $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); + + if ($padType === STR_PAD_LEFT) { + return str_repeat($padString, $repeatCount) . $lastString . $input; + } + + return $input . str_repeat($padString, $repeatCount) . $lastString; + } +} diff --git a/src/StringWrapper/Iconv.php b/src/StringWrapper/Iconv.php new file mode 100644 index 000000000..f3f2c6187 --- /dev/null +++ b/src/StringWrapper/Iconv.php @@ -0,0 +1,292 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @param string $encoding + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return iconv_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @param string $encoding + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return iconv_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + // automatically add "//IGNORE" to not stop converting on invalid characters + // invalid characters triggers a notice anyway + return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); + } +} diff --git a/src/StringWrapper/Intl.php b/src/StringWrapper/Intl.php new file mode 100644 index 000000000..536cb4363 --- /dev/null +++ b/src/StringWrapper/Intl.php @@ -0,0 +1,86 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @param string $encoding + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return mb_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @param string $encoding + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param boolean $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + return mb_convert_encoding($str, $toEncoding, $fromEncoding); + } +} diff --git a/src/StringWrapper/Native.php b/src/StringWrapper/Native.php new file mode 100644 index 000000000..505109880 --- /dev/null +++ b/src/StringWrapper/Native.php @@ -0,0 +1,133 @@ +convertEncoding = $encodingUpper; + } + + if ($convertEncoding !== null) { + if ($encodingUpper !== strtoupper($convertEncoding)) { + throw new Exception\InvalidArgumentException( + 'Wrapper doesn\'t support to convert between character encodings' + ); + } + + $this->convertEncoding = $encodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Returns the length of the given string + * + * @param string $str + * @return int|false + */ + public function strlen($str) + { + return strlen($str); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return substr($str, $offset, $length); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} diff --git a/src/StringWrapper/StringWrapperInterface.php b/src/StringWrapper/StringWrapperInterface.php new file mode 100644 index 000000000..dfe1550ae --- /dev/null +++ b/src/StringWrapper/StringWrapperInterface.php @@ -0,0 +1,113 @@ +assertFalse(ErrorHandler::started()); + $this->assertSame(0, ErrorHandler::getNestedLevel()); ErrorHandler::start(); - $this->assertTrue(ErrorHandler::started()); + $this->assertSame(1, ErrorHandler::getNestedLevel()); + + ErrorHandler::start(); + $this->assertSame(2, ErrorHandler::getNestedLevel()); ErrorHandler::stop(); - $this->assertFalse(ErrorHandler::started()); + $this->assertSame(1, ErrorHandler::getNestedLevel()); + + ErrorHandler::stop(); + $this->assertSame(0, ErrorHandler::getNestedLevel()); } - public function testStartThrowsLogicException() + public function testClean() { ErrorHandler::start(); + $this->assertSame(1, ErrorHandler::getNestedLevel()); - $this->setExpectedException('Zend\Stdlib\Exception\LogicException'); ErrorHandler::start(); + $this->assertSame(2, ErrorHandler::getNestedLevel()); + + ErrorHandler::clean(); + $this->assertSame(0, ErrorHandler::getNestedLevel()); } - public function testStopThrowsLogicException() + public function testStarted() { - $this->setExpectedException('Zend\Stdlib\Exception\LogicException'); + $this->assertFalse(ErrorHandler::started()); + + ErrorHandler::start(); + $this->assertTrue(ErrorHandler::started()); + ErrorHandler::stop(); + $this->assertFalse(ErrorHandler::started()); } public function testReturnCatchedError() @@ -65,7 +80,7 @@ public function testThrowCatchedError() ErrorHandler::stop(true); } - public function testAddErrors() + public function testAddError() { ErrorHandler::start(); ErrorHandler::addError(1, 'test-msg1', 'test-file1', 100); diff --git a/test/FilterTest.php b/test/FilterTest.php new file mode 100644 index 000000000..481b6481c --- /dev/null +++ b/test/FilterTest.php @@ -0,0 +1,55 @@ +assertTrue($hasValidation->filter('hasFoo')); + $this->assertTrue($hasValidation->filter('Foo::hasFoo')); + $this->assertFalse($hasValidation->filter('FoohasFoo')); + $this->assertFalse($hasValidation->filter('Bar::FoohasFoo')); + $this->assertFalse($hasValidation->filter('hAsFoo')); + $this->assertFalse($hasValidation->filter('Blubb::hAsFoo')); + $this->assertFalse($hasValidation->filter(get_class($this). '::hAsFoo')); + } + + public function testGetValidation() + { + $hasValidation = new GetFilter(); + $this->assertTrue($hasValidation->filter('getFoo')); + $this->assertTrue($hasValidation->filter('Bar::getFoo')); + $this->assertFalse($hasValidation->filter('GetFooBar')); + $this->assertFalse($hasValidation->filter('Foo::GetFooBar')); + $this->assertFalse($hasValidation->filter('GETFoo')); + $this->assertFalse($hasValidation->filter('Blubb::GETFoo')); + $this->assertFalse($hasValidation->filter(get_class($this).'::GETFoo')); + } + + public function testIsValidation() + { + $hasValidation = new IsFilter(); + $this->assertTrue($hasValidation->filter('isFoo')); + $this->assertTrue($hasValidation->filter('Blubb::isFoo')); + $this->assertFalse($hasValidation->filter('IsFooBar')); + $this->assertFalse($hasValidation->filter('Foo::IsFooBar')); + $this->assertFalse($hasValidation->filter('ISFoo')); + $this->assertFalse($hasValidation->filter('Bar::ISFoo')); + $this->assertFalse($hasValidation->filter(get_class($this).'::ISFoo')); + } + +} diff --git a/test/HydratorClosureStrategyTest.php b/test/HydratorClosureStrategyTest.php new file mode 100644 index 000000000..ae4d227ef --- /dev/null +++ b/test/HydratorClosureStrategyTest.php @@ -0,0 +1,126 @@ +hydrator = new ObjectProperty(); + } + + public function testAddingStrategy() + { + $this->assertAttributeCount(0, 'strategies', $this->hydrator); + + $this->hydrator->addStrategy('myStrategy', new ClosureStrategy()); + + $this->assertAttributeCount(1, 'strategies', $this->hydrator); + } + + public function testCheckStrategyEmpty() + { + $this->assertFalse($this->hydrator->hasStrategy('myStrategy')); + } + + public function testCheckStrategyNotEmpty() + { + $this->hydrator->addStrategy('myStrategy', new ClosureStrategy()); + + $this->assertTrue($this->hydrator->hasStrategy('myStrategy')); + } + + public function testRemovingStrategy() + { + $this->assertAttributeCount(0, 'strategies', $this->hydrator); + + $this->hydrator->addStrategy('myStrategy', new ClosureStrategy()); + $this->assertAttributeCount(1, 'strategies', $this->hydrator); + + $this->hydrator->removeStrategy('myStrategy'); + $this->assertAttributeCount(0, 'strategies', $this->hydrator); + } + + public function testRetrieveStrategy() + { + $strategy = new ClosureStrategy(); + $this->hydrator->addStrategy('myStrategy', $strategy); + + $this->assertEquals($strategy, $this->hydrator->getStrategy('myStrategy')); + } + + public function testExtractingObjects() + { + $this->hydrator->addStrategy('field1', new ClosureStrategy( + function($value) { + return sprintf('%s', $value); + }, + null + )); + $this->hydrator->addStrategy('field2', new ClosureStrategy( + function($value) { + return sprintf('hello, %s!', $value); + }, + null + )); + + $entity = new TestAsset\HydratorClosureStrategyEntity(111, 'world'); + $values = $this->hydrator->extract($entity); + + $this->assertEquals(111, $values['field1']); + $this->assertEquals('hello, world!', $values['field2']); + } + + public function testHydratingObjects() + { + $this->hydrator->addStrategy('field2', new ClosureStrategy( + null, + function($value) { + return sprintf('hello, %s!', $value); + } + )); + $this->hydrator->addStrategy('field3', new ClosureStrategy( + null, + function($value) { + return new TestAsset\HydratorClosureStrategyEntity($value, sprintf('111%s', $value)); + } + )); + + $entity = new TestAsset\HydratorClosureStrategyEntity(111, 'world'); + + $values = $this->hydrator->extract($entity); + $values['field3'] = 333; + + $this->assertCount(2, (array)$entity); + $this->hydrator->hydrate($values, $entity); + $this->assertCount(3, (array)$entity); + + $this->assertInstanceOf('ZendTest\Stdlib\TestAsset\HydratorClosureStrategyEntity', $entity->field3); + } + +} diff --git a/test/HydratorTest.php b/test/HydratorTest.php index 02377a130..1c6938185 100644 --- a/test/HydratorTest.php +++ b/test/HydratorTest.php @@ -12,10 +12,21 @@ use Zend\Stdlib\Hydrator\ClassMethods; use Zend\Stdlib\Hydrator\Reflection; +use Zend\Stdlib\Hydrator\ObjectProperty; +use Zend\Stdlib\Hydrator\ArraySerializable; +use Zend\Stdlib\Hydrator\Filter\FilterComposite; use ZendTest\Stdlib\TestAsset\ClassMethodsCamelCase; +use ZendTest\Stdlib\TestAsset\ClassMethodsFilterProviderInterface; use ZendTest\Stdlib\TestAsset\ClassMethodsUnderscore; use ZendTest\Stdlib\TestAsset\ClassMethodsCamelCaseMissing; +use ZendTest\Stdlib\TestAsset\ClassMethodsInvalidParameter; use ZendTest\Stdlib\TestAsset\Reflection as ReflectionAsset; +use ZendTest\Stdlib\TestAsset\ReflectionFilter; +use ZendTest\Stdlib\TestAsset\ObjectProperty as ObjectPropertyAsset; +use ZendTest\Stdlib\TestAsset\ArraySerializable as ArraySerializableAsset; +use Zend\Stdlib\Hydrator\Strategy\DefaultStrategy; +use Zend\Stdlib\Hydrator\Strategy\SerializableStrategy; + /** * @category Zend @@ -41,6 +52,11 @@ class HydratorTest extends \PHPUnit_Framework_TestCase */ protected $classMethodsUnderscore; + /** + * @var ClassMethodsInvalidParameter + */ + protected $classMethodsInvalidParameter; + /** * @var ReflectionAsset */ @@ -51,7 +67,9 @@ public function setUp() $this->classMethodsCamelCase = new ClassMethodsCamelCase(); $this->classMethodsCamelCaseMissing = new ClassMethodsCamelCaseMissing(); $this->classMethodsUnderscore = new ClassMethodsUnderscore(); + $this->classMethodsInvalidParameter = new ClassMethodsInvalidParameter(); $this->reflection = new ReflectionAsset; + $this->classMethodsInvalidParameter = new ClassMethodsInvalidParameter(); } public function testInitiateValues() @@ -163,6 +181,16 @@ public function testHydratorClassMethodsUnderscore() $this->assertEquals($test->hasBar(), false); } + public function testHydratorClassMethodsOptions() + { + $hydrator = new ClassMethods(); + $this->assertTrue($hydrator->getUnderscoreSeparatedKeys()); + $hydrator->setOptions(array('underscoreSeparatedKeys' => false)); + $this->assertFalse($hydrator->getUnderscoreSeparatedKeys()); + $hydrator->setUnderscoreSeparatedKeys(true); + $this->assertTrue($hydrator->getUnderscoreSeparatedKeys()); + } + public function testHydratorClassMethodsIgnoresInvalidValues() { $hydrator = new ClassMethods(true); @@ -189,6 +217,39 @@ public function testHydratorClassMethodsDefaultBehaviorIsConvertUnderscoreToCame $this->assertEquals($test->getFooBarBaz(), 'bar'); } + public function testRetrieveWildStrategyAndOther() + { + $hydrator = new ClassMethods(); + $hydrator->addStrategy('default', new DefaultStrategy()); + $hydrator->addStrategy('*', new SerializableStrategy('phpserialize')); + $default = $hydrator->getStrategy('default'); + $this->assertEquals(get_class($default), 'Zend\Stdlib\Hydrator\Strategy\DefaultStrategy'); + $serializable = $hydrator->getStrategy('*'); + $this->assertEquals(get_class($serializable), 'Zend\Stdlib\Hydrator\Strategy\SerializableStrategy'); + } + + public function testUseWildStrategyByDefault() + { + $hydrator = new ClassMethods(); + $datas = $hydrator->extract($this->classMethodsUnderscore); + $this->assertEquals($datas['foo_bar'], '1'); + $hydrator->addStrategy('*', new SerializableStrategy('phpserialize')); + $datas = $hydrator->extract($this->classMethodsUnderscore); + $this->assertEquals($datas['foo_bar'], 's:1:"1";'); + } + + public function testUseWildStrategyAndOther() + { + $hydrator = new ClassMethods(); + $datas = $hydrator->extract($this->classMethodsUnderscore); + $this->assertEquals($datas['foo_bar'], '1'); + $hydrator->addStrategy('foo_bar', new DefaultStrategy()); + $hydrator->addStrategy('*', new SerializableStrategy('phpserialize')); + $datas = $hydrator->extract($this->classMethodsUnderscore); + $this->assertEquals($datas['foo_bar'], '1'); + $this->assertEquals($datas['foo_bar_baz'], 's:1:"2";'); + } + public function testHydratorClassMethodsCamelCaseWithSetterMissing() { $hydrator = new ClassMethods(false); @@ -203,4 +264,137 @@ public function testHydratorClassMethodsCamelCaseWithSetterMissing() $this->assertEquals($test->getFooBar(), 'foo'); $this->assertEquals($test->getFooBarBaz(), '2'); } + + public function testHydratorClassMethodsManipulateFilter() + { + $hydrator = new ClassMethods(false); + $datas = $hydrator->extract($this->classMethodsCamelCase); + + $this->assertTrue(isset($datas['fooBar'])); + $this->assertEquals($datas['fooBar'], '1'); + $this->assertTrue(isset($datas['fooBarBaz'])); + $this->assertFalse(isset($datas['foo_bar'])); + $this->assertTrue(isset($datas['isFoo'])); + $this->assertEquals($datas['isFoo'], true); + $this->assertTrue(isset($datas['isBar'])); + $this->assertEquals($datas['isBar'], true); + $this->assertTrue(isset($datas['hasFoo'])); + $this->assertEquals($datas['hasFoo'], true); + $this->assertTrue(isset($datas['hasBar'])); + $this->assertEquals($datas['hasBar'], true); + + $hydrator->removeFilter('has'); + $datas = $hydrator->extract($this->classMethodsCamelCase); + $this->assertTrue(isset($datas['hasFoo'])); //method is getHasFoo + $this->assertFalse(isset($datas['hasBar'])); //method is hasBar + } + + public function testHydratorClassMethodsWithCustomFilter() + { + $hydrator = new ClassMethods(false); + $datas = $hydrator->extract($this->classMethodsCamelCase); + $hydrator->addFilter("exclude", + function($property) { + list($class, $method) = explode('::', $property); + + if($method == 'getHasFoo') { + return false; + } + + return true; + }, FilterComposite::CONDITION_AND + ); + + $datas = $hydrator->extract($this->classMethodsCamelCase); + $this->assertFalse(isset($datas['hasFoo'])); + } + + /** + * @dataProvider filterProvider + */ + public function testArraySerializableFilter($hydrator, $serializable) + { + $this->assertSame( + array( + "foo" => "bar", + "bar" => "foo", + "blubb" => "baz", + "quo" => "blubb" + ), + $hydrator->extract($serializable) + ); + + $hydrator->addFilter("foo", function($property) { + if ($property == "foo") { + return false; + } + return true; + }); + + $this->assertSame( + array( + "bar" => "foo", + "blubb" => "baz", + "quo" => "blubb" + ), + $hydrator->extract($serializable) + ); + + $hydrator->addFilter("len", function($property) { + if (strlen($property) !== 3) { + return false; + } + return true; + }, FilterComposite::CONDITION_AND); + + $this->assertSame( + array( + "bar" => "foo", + "quo" => "blubb" + ), + $hydrator->extract($serializable) + ); + + $hydrator->removeFilter("len"); + $hydrator->removeFilter("foo"); + + $this->assertSame( + array( + "foo" => "bar", + "bar" => "foo", + "blubb" => "baz", + "quo" => "blubb" + ), + $hydrator->extract($serializable) + ); + } + + public function filterProvider() + { + return array( + array(new ObjectProperty(), new ObjectPropertyAsset), + array(new ArraySerializable(), new ArraySerializableAsset), + array(new Reflection(), new ReflectionFilter) + ); + } + + public function testHydratorClassMethodsWithInvalidNumberOfParameters() + { + $hydrator = new ClassMethods(false); + $datas = $hydrator->extract($this->classMethodsInvalidParameter); + + $this->assertTrue($datas['hasBar']); + $this->assertEquals('Bar', $datas['foo']); + $this->assertFalse($datas['isBla']); + } + + public function testObjectBasedFilters() + { + $hydrator = new ClassMethods(false); + $foo = new ClassMethodsFilterProviderInterface(); + $data = $hydrator->extract($foo); + $this->assertFalse(array_key_exists("filter", $data)); + $this->assertSame("bar", $data["foo"]); + $this->assertSame("foo", $data["bar"]); + } } diff --git a/test/OptionsTest.php b/test/OptionsTest.php index cb89f0e17..d073a432b 100644 --- a/test/OptionsTest.php +++ b/test/OptionsTest.php @@ -47,7 +47,6 @@ public function testNonStrictOptionsDoesNotThrowException() } } - public function testConstructionWithNull() { try { @@ -65,4 +64,34 @@ public function testUnsetting() unset($options->testField); $this->assertEquals(false, isset($options->test_field)); } + + public function testUnsetThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $options = new TestOptions; + unset($options->foobarField); + } + + public function testGetThrowsBadMethodCallException() + { + $this->setExpectedException('BadMethodCallException'); + $options = new TestOptions(); + $options->fieldFoobar; + } + + public function testSetFromArrayAcceptsArray() + { + $array = array('test_field' => 3); + $options = new TestOptions(); + + $this->assertSame($options, $options->setFromArray($array)); + $this->assertEquals(3, $options->test_field); + } + + public function testSetFromArrayThrowsInvalidArgumentException() + { + $this->setExpectedException('InvalidArgumentException'); + $options = new TestOptions; + $options->setFromArray('asd'); + } } diff --git a/test/Strategy/SerializableStrategyTest.php b/test/Strategy/SerializableStrategyTest.php new file mode 100644 index 000000000..f36980de2 --- /dev/null +++ b/test/Strategy/SerializableStrategyTest.php @@ -0,0 +1,53 @@ +setExpectedException('Zend\Stdlib\Exception\InvalidArgumentException'); + $serializerStrategy = new SerializableStrategy(false); + } + + public function testUseBadSerilizerObject() + { + $serializer = Serializer::factory('phpserialize'); + $serializerStrategy = new SerializableStrategy($serializer); + $this->assertEquals($serializer, $serializerStrategy->getSerializer()); + } + + public function testUseBadSerilizerString() + { + $serializerStrategy = new SerializableStrategy('phpserialize'); + $this->assertEquals('Zend\Serializer\Adapter\PhpSerialize', get_class($serializerStrategy->getSerializer())); + } + + public function testCanSerialize() + { + $serializer = Serializer::factory('phpserialize'); + $serializerStrategy = new SerializableStrategy($serializer); + $serialized = $serializerStrategy->extract('foo'); + $this->assertEquals($serialized, 's:3:"foo";'); + } + + public function testCanUnserialize() + { + $serializer = Serializer::factory('phpserialize'); + $serializerStrategy = new SerializableStrategy($serializer); + $serialized = $serializerStrategy->hydrate('s:3:"foo";'); + $this->assertEquals($serialized, 'foo'); + } +} diff --git a/test/StringUtilsTest.php b/test/StringUtilsTest.php new file mode 100644 index 000000000..694d68ebf --- /dev/null +++ b/test/StringUtilsTest.php @@ -0,0 +1,160 @@ +assertTrue(StringUtils::isSingleByteEncoding($encoding)); + } + + public function getNonSingleByteEncodings() + { + return array( + array('UTf-8'), + array('UTf-16'), + array('usC-2'), + array('CESU-8'), + ); + } + + /** + * @dataProvider getNonSingleByteEncodings + * @param string $encoding + */ + public function testIsSingleByteEncodingReturnsFalse($encoding) + { + $this->assertFalse(StringUtils::isSingleByteEncoding($encoding)); + } + + public function testGetWrapper() + { + $wrapper = StringUtils::getWrapper('ISO-8859-1'); + if (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } else { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Native', $wrapper); + } + + try { + $wrapper = StringUtils::getWrapper('UTF-8'); + if (extension_loaded('intl')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Intl', $wrapper); + } elseif (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } + } catch (Exception $e) { + if (extension_loaded('intl') + || extension_loaded('mbstring') + || extension_loaded('iconv') + ) { + $this->fail("Failed to get intl, mbstring or iconv wrapper for UTF-8"); + } + } + + try { + $wrapper = StringUtils::getWrapper('UTF-8', 'ISO-8859-1'); + if (extension_loaded('mbstring')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\MbString', $wrapper); + } elseif (extension_loaded('iconv')) { + $this->assertInstanceOf('Zend\Stdlib\StringWrapper\Iconv', $wrapper); + } + } catch (Exception $e) { + if (extension_loaded('mbstring') || extension_loaded('iconv')) { + $this->fail("Failed to get mbstring or iconv wrapper for UTF-8 and ISO-8859-1"); + } + } + } + + public function getUtf8StringValidity() + { + return array( + // valid + array('', true), + array("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + . "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" + . ' !"#$%&\'()*+,-./0123456789:;<=>?' + . '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_' + . '`abcdefghijklmnopqrstuvwxyz{|}~', + true + ), + + // invalid + array(true, false), + array(123, false), + array(123.45, false), + array("\xFF", false), + array("\x90a", false), + ); + } + + /** + * @dataProvider getUtf8StringValidity + * @param string $str + * @param boolean $valid + */ + public function testIsValidUtf8($str, $valid) + { + $this->assertSame($valid, StringUtils::isValidUtf8($str)); + } + + public function testHasPcreUnicodeSupport() + { + ErrorHandler::start(); + $expected = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1; + ErrorHandler::stop(); + + $this->assertSame($expected, StringUtils::hasPcreUnicodeSupport()); + } +} diff --git a/test/StringWrapper/CommonStringWrapperTest.php b/test/StringWrapper/CommonStringWrapperTest.php new file mode 100644 index 000000000..7546c0d7b --- /dev/null +++ b/test/StringWrapper/CommonStringWrapperTest.php @@ -0,0 +1,298 @@ +getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strlen($str); + $this->assertSame($expected, $result); + } + + public function substrProvider() + { + return array( + array('ascii', 'abcdefghijkl', 1, 5, 'bcdef'), + array('utf-8', 'abcdefghijkl', 1, 5, 'bcdef'), + array('utf-8', 'äöüß', 1, 2, 'öü'), + ); + } + + /** + * @dataProvider substrProvider + * @param string $encoding + * @param string $str + * @param int $offset + * @param int|null $length + * @param mixed $expected + */ + public function testSubstr($encoding, $str, $offset, $length, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->substr($str, $offset, $length); + $this->assertSame($expected, $result); + } + + public function strposProvider() + { + return array( + array('ascii', 'abcdefghijkl', 'g', 3, 6), + array('utf-8', 'abcdefghijkl', 'g', 3, 6), + array('utf-8', 'äöüß', 'ü', 1, 2), + ); + } + + /** + * @dataProvider strposProvider + * @param string $encoding + * @param string $haystack + * @param string $needle + * @param int $offset + * @param mixed $expected + */ + public function testStrpos($encoding, $haystack, $needle, $offset, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strpos($haystack, $needle, $offset); + $this->assertSame($expected, $result); + } + + public function convertProvider() + { + return array( + array('ascii', 'ascii', 'abc', 'abc'), + array('ascii', 'utf-8', 'abc', 'abc'), + array('utf-8', 'ascii', 'abc', 'abc'), + array('utf-8', 'iso-8859-15', '€', "\xA4"), + array('utf-8', 'iso-8859-16', '€', "\xA4"), // ISO-8859-16 is wrong @ mbstring + ); + } + + /** + * @dataProvider convertProvider + * @param string $str + * @param string $encoding + * @param string $convertEncoding + * @param mixed $expected + */ + public function testConvert($encoding, $convertEncoding, $str, $expected) + { + $wrapper = $this->getWrapper($encoding, $convertEncoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} or {$convertEncoding} not supported"); + } + + $result = $wrapper->convert($str); + $this->assertSame($expected, $result); + + // backword + $result = $wrapper->convert($expected, true); + $this->assertSame($str, $result); + } + + public function wordWrapProvider() + { + return array( + // Standard cut tests + 'cut-single-line' => + array('utf-8', 'äbüöcß', 2, ' ', true, 'äb üö cß'), + 'cut-multi-line' => + array('utf-8', 'äbüöc ß äbüöcß', 2, ' ', true, 'äb üö c ß äb üö cß'), + 'cut-multi-line-short-words' => + array('utf-8', 'Ä very long wöööööööööööörd.', 8, "\n", true, + "Ä very\nlong\nwööööööö\nööööörd."), + 'cut-multi-line-with-previous-new-lines' => + array('utf-8', "Ä very\nlong wöööööööööööörd.", 8, "\n", false, + "Ä very\nlong\nwöööööööööööörd."), + 'long-break' => + array('utf-8', "Ä very
long wöö
öööööööö
öörd.", 8, '
', false, + "Ä very
long wöö
öööööööö
öörd."), + + // Alternative cut tests + 'cut-beginning-single-space' => + array('utf-8', ' äüöäöü', 3, ' ', true, ' äüö äöü'), + 'cut-ending-single-space' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'cut-ending-single-space-with-non-space-divider' => + array('utf-8', 'äöüäöü ', 3, '-', true, 'äöü-äöü-'), + 'cut-ending-two-spaces' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'no-cut-ending-single-space' => + array('utf-8', '12345 ', 5, '-', false, '12345-'), + 'no-cut-ending-two-spaces' => + array('utf-8', '12345 ', 5, '-', false, '12345- '), + 'cut-ending-three-spaces' => + array('utf-8', 'äüöäöü ', 3, ' ', true, 'äüö äöü '), + 'cut-ending-two-breaks' => + array('utf-8', 'äüöäöü--', 3, '-', true, 'äüö-äöü--'), + 'cut-tab' => + array('utf-8', "äbü\töcß", 3, ' ', true, "äbü \töc ß"), + 'cut-new-line-with-space' => + array('utf-8', "äbü\nößt", 3, ' ', true, "äbü \nöß t"), + 'cut-new-line-with-new-line' => + array('utf-8', "äbü\nößte", 3, "\n", true, "äbü\nößt\ne"), + + // Break cut tests + 'cut-break-before' => + array('ascii', 'foobar-foofoofoo', 8, '-', true, 'foobar-foofoofo-o'), + 'cut-break-with' => + array('ascii', 'foobar-foobar', 6, '-', true, 'foobar-foobar'), + 'cut-break-within' => + array('ascii', 'foobar-foobar', 7, '-', true, 'foobar-foobar'), + 'cut-break-within-end' => + array('ascii', 'foobar-', 7, '-', true, 'foobar-'), + 'cut-break-after' => + array('ascii', 'foobar-foobar', 5, '-', true, 'fooba-r-fooba-r'), + + // Standard no-cut tests + 'no-cut-single-line' => + array('utf-8', 'äbüöcß', 2, ' ', false, 'äbüöcß'), + 'no-cut-multi-line' => + array('utf-8', 'äbüöc ß äbüöcß', 2, "\n", false, "äbüöc\nß\näbüöcß"), + 'no-cut-multi-word' => + array('utf-8', 'äöü äöü äöü', 5, "\n", false, "äöü\näöü\näöü"), + + // Break no-cut tests + 'no-cut-break-before' => + array('ascii', 'foobar-foofoofoo', 8, '-', false, 'foobar-foofoofoo'), + 'no-cut-break-with' => + array('ascii', 'foobar-foobar', 6, '-', false, 'foobar-foobar'), + 'no-cut-break-within' => + array('ascii', 'foobar-foobar', 7, '-', false, 'foobar-foobar'), + 'no-cut-break-within-end' => + array('ascii', 'foobar-', 7, '-', false, 'foobar-'), + 'no-cut-break-after' => + array('ascii', 'foobar-foobar', 5, '-', false, 'foobar-foobar'), + ); + } + + /** + * @dataProvider wordWrapProvider + * @param string $encoding + * @param string $str + * @param integer $width + * @param string $break + * @param boolean $cut + * @param mixed $expected + */ + public function testWordWrap($encoding, $string, $width, $break, $cut, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->wordWrap($string, $width, $break, $cut); + $this->assertSame($expected, $result); + } + + public function testWordWrapInvalidArgument() + { + $wrapper = $this->getWrapper(); + if (!$wrapper) { + $this->fail("Can't instantiate wrapper"); + } + + $this->setExpectedException( + 'Zend\Stdlib\Exception\InvalidArgumentException', + "Cannot force cut when width is zero" + ); + $wrapper->wordWrap('a', 0, "\n", true); + } + + public function strPadProvider() + { + return array( + // single-byte + 'left-padding_single-byte' => + array('ascii', 'aaa', 5, 'o', STR_PAD_LEFT, 'ooaaa'), + 'center-padding_single-byte' => + array('ascii', 'aaa', 6, 'o', STR_PAD_BOTH, 'oaaaoo'), + 'right-padding_single-byte' => + array('ascii', 'aaa', 5, 'o', STR_PAD_RIGHT, 'aaaoo'), + + // multi-byte + 'left-padding_multi-byte' => + array('utf-8', 'äää', 5, 'ö', STR_PAD_LEFT, 'ööäää'), + 'center-padding_multi-byte' => + array('utf-8', 'äää', 6, 'ö', STR_PAD_BOTH, 'öäääöö'), + 'right-padding_multi-byte' => + array('utf-8', 'äää', 5, 'ö', STR_PAD_RIGHT, 'äääöö'), + + // ZF-12186 + 'input-longer-than-pad-length' => + array('utf-8', 'äääöö', 2, 'ö', STR_PAD_RIGHT, 'äääöö'), + 'input-same-as-pad-length' => + array('utf-8', 'äääöö', 5, 'ö', STR_PAD_RIGHT, 'äääöö'), + 'negative-pad-length' => + array('utf-8', 'äääöö', -2, 'ö', STR_PAD_RIGHT, 'äääöö'), + ); + } + + /** + * @dataProvider strPadProvider + * @param string $encoding + * @param string $input + * @param integer $padLength + * @param string $padString + * @param integer $padType + * @param mixed $expected + * + * @group ZF-12186 + */ + public function testStrPad($encoding, $input, $padLength, $padString, $padType, $expected) + { + $wrapper = $this->getWrapper($encoding); + if (!$wrapper) { + $this->markTestSkipped("Encoding {$encoding} not supported"); + } + + $result = $wrapper->strPad($input, $padLength, $padString, $padType); + $this->assertSame($expected, $result); + } +} diff --git a/test/StringWrapper/IconvTest.php b/test/StringWrapper/IconvTest.php new file mode 100644 index 000000000..ec3b0db24 --- /dev/null +++ b/test/StringWrapper/IconvTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/iconv'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = Iconv::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!Iconv::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new Iconv(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/test/StringWrapper/IntlTest.php b/test/StringWrapper/IntlTest.php new file mode 100644 index 000000000..d3092f60b --- /dev/null +++ b/test/StringWrapper/IntlTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/intl'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = Intl::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!Intl::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new Intl(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/test/StringWrapper/MbStringTest.php b/test/StringWrapper/MbStringTest.php new file mode 100644 index 000000000..d024afaa6 --- /dev/null +++ b/test/StringWrapper/MbStringTest.php @@ -0,0 +1,49 @@ +fail('Missing expected Zend\Stdlib\Exception\ExtensionNotLoadedException'); + } catch (Exception\ExtensionNotLoadedException $e) { + $this->markTestSkipped('Missing ext/mbstring'); + } + } + + parent::setUp(); + } + + protected function getWrapper($encoding = null, $convertEncoding = null) + { + if ($encoding === null) { + $supportedEncodings = MbString::getSupportedEncodings(); + $encoding = array_shift($supportedEncodings); + } + + if (!MbString::isSupported($encoding, $convertEncoding)) { + return false; + } + + $wrapper = new MbString(); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/test/StringWrapper/NativeTest.php b/test/StringWrapper/NativeTest.php new file mode 100644 index 000000000..b54e41daa --- /dev/null +++ b/test/StringWrapper/NativeTest.php @@ -0,0 +1,34 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } +} diff --git a/test/TestAsset/ArraySerializable.php b/test/TestAsset/ArraySerializable.php new file mode 100644 index 000000000..96f1e74f9 --- /dev/null +++ b/test/TestAsset/ArraySerializable.php @@ -0,0 +1,53 @@ +data = array( + "foo" => "bar", + "bar" => "foo", + "blubb" => "baz", + "quo" => "blubb" + ); + } + + /** + * Exchange internal values from provided array + * + * @param array $array + * @return void + */ + public function exchangeArray(array $array) + { + $this->data = $array; + } + + /** + * Return an array representation of the object + * + * @return array + */ + public function getArrayCopy() + { + return $this->data; + } +} diff --git a/test/TestAsset/ClassMethodsFilterProviderInterface.php b/test/TestAsset/ClassMethodsFilterProviderInterface.php new file mode 100644 index 000000000..b24b8210a --- /dev/null +++ b/test/TestAsset/ClassMethodsFilterProviderInterface.php @@ -0,0 +1,70 @@ +addFilter("get", new GetFilter()); + $excludes = new FilterComposite(); + $excludes->addFilter( + "servicemanager", + new MethodMatchFilter("getServiceManager"), + FilterComposite::CONDITION_AND + ); + $excludes->addFilter( + "eventmanager", + new MethodMatchFilter("getEventManager"), + FilterComposite::CONDITION_AND + ); + $filterComposite->addFilter("excludes", $excludes, FilterComposite::CONDITION_AND); + + return $filterComposite; + } +} diff --git a/test/TestAsset/ClassMethodsInvalidParameter.php b/test/TestAsset/ClassMethodsInvalidParameter.php new file mode 100644 index 000000000..cf9338865 --- /dev/null +++ b/test/TestAsset/ClassMethodsInvalidParameter.php @@ -0,0 +1,43 @@ +filterComposite = new FilterComposite(); + } + + public function testValidationAdd() + { + $this->assertTrue($this->filterComposite->filter("foo")); + $this->filterComposite->addFilter("has", + function($property) { + return false; + } + ); + $this->assertFalse($this->filterComposite->filter("foo")); + } + + public function testValidationRemove() + { + $this->filterComposite->addFilter("has", + function($property) { + return false; + } + ); + $this->assertFalse($this->filterComposite->filter("foo")); + $this->filterComposite->removeFilter("has"); + $this->assertTrue($this->filterComposite->filter("foo")); + } + + public function testValidationHas() + { + $this->filterComposite->addFilter("has", + function($property) { + return false; + } + ); + $this->assertFalse($this->filterComposite->filter("foo")); + $this->assertTrue($this->filterComposite->hasFilter("has")); + } + + public function testComplexValidation() + { + $this->filterComposite->addFilter("has", new \Zend\Stdlib\Hydrator\Filter\HasFilter()); + $this->filterComposite->addFilter("get", new \Zend\Stdlib\Hydrator\Filter\GetFilter()); + $this->filterComposite->addFilter("is", new \Zend\Stdlib\Hydrator\Filter\IsFilter()); + + $this->filterComposite->addFilter("exclude", + function($property) { + $method = substr($property, strpos($property, '::')); + + if ($method === 'getServiceLocator') { + return false; + } + + return true; + }, FilterComposite::CONDITION_AND + ); + + $this->assertTrue($this->filterComposite->filter('getFooBar')); + $this->assertFalse($this->filterComposite->filter('getServiceLocator')); + } + + public function testConstructorInjection() + { + $andCondition = array( + 'servicelocator' => function($property) { + if($property === 'getServiceLocator') { + return false; + } + return true; + }, + 'foobar' => function($property) { + if($property === 'getFooBar') { + return false; + } + return true; + } + ); + $orCondition = array( + 'has' => new \Zend\Stdlib\Hydrator\Filter\HasFilter(), + 'get' => new \Zend\Stdlib\Hydrator\Filter\GetFilter() + ); + $filterComposite = new FilterComposite($orCondition, $andCondition); + + $this->assertFalse($filterComposite->filter('getFooBar')); + $this->assertFalse($filterComposite->filter('geTFooBar')); + $this->assertFalse($filterComposite->filter('getServiceLocator')); + $this->assertTrue($filterComposite->filter('getFoo')); + $this->assertTrue($filterComposite->filter('hasFoo')); + } + + public function testWithOnlyAndFiltersAdded() + { + $filter = new FilterComposite(); + $filter->addFilter("foobarbaz", function($property) { + return true; + }, FilterComposite::CONDITION_AND); + $filter->addFilter("foobar", function($property) { + return true; + }, FilterComposite::CONDITION_AND); + $this->assertTrue($filter->filter("foo")); + } + + public function testWithOnlyOrFiltersAdded() + { + $filter = new FilterComposite(); + $filter->addFilter("foobarbaz", function($property) { + return true; + }); + $filter->addFilter("foobar", function($property) { + return false; + }); + $this->assertTrue($filter->filter("foo")); + } + + public function testWithComplexCompositeAdded() + { + $filter1 = new FilterComposite(); + $filter1->addFilter("foobarbaz", function($property) { + return true; + }); + $filter1->addFilter("foobar", function($property) { + return false; + }); + $filter2 = new FilterComposite(); + $filter2->addFilter("bar", function($property) { + return true; + }, FilterComposite::CONDITION_AND); + $filter2->addFilter("barblubb", function($property) { + return true; + }, FilterComposite::CONDITION_AND); + $this->assertTrue($filter1->filter("foo")); + $this->assertTrue($filter2->filter("foo")); + $filter1->addFilter("bar", $filter2); + $this->assertTrue($filter1->filter("blubb")); + + $filter1->addFilter("blubb", function($property) { return false; }, FilterComposite::CONDITION_AND); + $this->assertFalse($filter1->filter("test")); + } + + /** + * @expectedException Zend\Stdlib\Exception\InvalidArgumentException + * @expectedExceptionMessage The value of test should be either a callable + * or an instance of Zend\Stdlib\Hydrator\Filter\FilterInterface + */ + public function testInvalidParameterConstructorInjection() + { + $andCondition = array('foo' => 'bar'); + $orCondition = array('test' => 'blubb'); + + new FilterComposite($orCondition, $andCondition); + } + + /** + * @expectedException Zend\Stdlib\Exception\InvalidArgumentException + * @expectedExceptionMessage The value of foo should be either a callable + * or an instance of Zend\Stdlib\Hydrator\Filter\FilterInterface + */ + public function testInvalidFilterInjection() + { + $this->filterComposite->addFilter('foo', 'bar'); + } +} diff --git a/test/TestAsset/HydratorClosureStrategyEntity.php b/test/TestAsset/HydratorClosureStrategyEntity.php new file mode 100644 index 000000000..bceaebb6b --- /dev/null +++ b/test/TestAsset/HydratorClosureStrategyEntity.php @@ -0,0 +1,24 @@ +field1 = $field1; + $this->field2 = $field2; + } +} diff --git a/test/TestAsset/ObjectProperty.php b/test/TestAsset/ObjectProperty.php new file mode 100644 index 000000000..6f412078a --- /dev/null +++ b/test/TestAsset/ObjectProperty.php @@ -0,0 +1,34 @@ +foo = "bar"; + $this->bar = "foo"; + $this->blubb = "baz"; + $this->quo = "blubb"; + } + +} diff --git a/test/TestAsset/ReflectionFilter.php b/test/TestAsset/ReflectionFilter.php new file mode 100644 index 000000000..e74bb058e --- /dev/null +++ b/test/TestAsset/ReflectionFilter.php @@ -0,0 +1,34 @@ +foo = "bar"; + $this->bar = "foo"; + $this->blubb = "baz"; + $this->quo = "blubb"; + } + +}