From 0a87f322d2de54cdd519bee301a0045cdaa47f5d Mon Sep 17 00:00:00 2001 From: George Steel Date: Wed, 19 Jul 2023 17:33:03 +0100 Subject: [PATCH 1/4] Improve type inference for stateful container implementations Adds templates to the container and abstract placeholder view helper classes and documents the types used by each implementing view helper. A lot of incorrect docblocks have been fixed, additional magic methods documented and a decent reduction in the baseline. Several implementations store values as plain objects and were in places (parameter) type hinting on `stdClass`. All references to `stdClass` have been replaced with `object` and `instanceof` conditions replaced with `is_object`. There's no BC break because `object` is more general than `stdClass`. The main purpose here is that it's not possible to express object shapes with `stdClass` which is central to the significant improvements in type inference. Various public and protected methods have been marked `@internal` so that they can be made private in the next major, and there are a handful of deprecations for unused, un-documented and un-tested public methods. Signed-off-by: George Steel --- psalm-baseline.xml | 303 +++--------------- src/Helper/HeadLink.php | 94 +++--- src/Helper/HeadMeta.php | 90 ++++-- src/Helper/HeadScript.php | 107 ++++--- src/Helper/HeadStyle.php | 77 ++--- src/Helper/HeadTitle.php | 41 ++- src/Helper/InlineScript.php | 12 +- src/Helper/Placeholder.php | 7 +- src/Helper/Placeholder/Container.php | 8 +- .../Container/AbstractContainer.php | 60 ++-- .../Container/AbstractStandalone.php | 58 ++-- test/Helper/HeadLinkTest.php | 10 +- test/Helper/HeadMetaTest.php | 7 +- test/Helper/HeadScriptTest.php | 37 +-- test/Helper/HeadStyleTest.php | 13 +- test/Helper/HeadTitleTest.php | 51 +-- test/Helper/Placeholder/ContainerTest.php | 84 +++-- .../Placeholder/StandaloneContainerTest.php | 16 +- test/Helper/TestAsset/Bar.php | 1 + test/Helper/TestAsset/Foo.php | 1 + test/Helper/TestAsset/MockContainer.php | 8 + test/Helper/TestAsset/NonStringableObject.php | 9 + 22 files changed, 498 insertions(+), 596 deletions(-) create mode 100644 test/Helper/TestAsset/NonStringableObject.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index eadf3b3a1..01fd07451 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -225,18 +225,6 @@ - - $item - - - getContainer()->set($value)]]> - - - HeadLink - - - (object) $attributes - offsetSet @@ -252,7 +240,6 @@ $item autoEscape ? $this->escapeAttribute($attributes[$itemKey]) : $attributes[$itemKey]]]> autoEscape ? $this->escapeAttribute($value) : $value]]> - getSeparator()]]> $value @@ -269,42 +256,17 @@ $extras $href $href - $indent $index $item $item - $item $media $title $type $value - - HeadLink - - - $indent - $indent - - - href]]> - rel]]> - - - - - - $value - - - stdClass - $index - - $item - $index view]]> @@ -312,24 +274,12 @@ (string) $conditionalStylesheet - - getIndent - getSeparator - getWhitespace - setSeparator - - - ! $item instanceof stdClass - - - $item + $value - - - $item - + $value + offsetSet offsetUnset @@ -347,22 +297,21 @@ autoEscape ? $this->escapeAttribute($item->$type) : $item->$type]]> autoEscape ? $this->escapeAttribute($item->content) : $item->content]]> autoEscape ? $this->escapeAttribute($value) : $value]]> - getSeparator()]]> $type $value + $value $doctype $indent $index $item - $item $key $type $value - HeadMeta + $this isHtml5 @@ -376,20 +325,10 @@ $indent $indent - - type]]> - {$item->type}]]> - parent::__call($method, $args) parent::__call($method, $args) - - $value - - - offsetSet($index, $item)]]> - $index $index @@ -408,53 +347,43 @@ plugin - getIndent - getSeparator getWhitespace - setSeparator - - - offsetSet $content $content - $indent $index $index - attributes]]> attributes['conditional']]]> type]]> type]]> $key autoEscape ? $this->escapeAttribute($value) : $value]]> - getSeparator()]]> $value + + $attrs + $attrs + $content - $indent $index $item - $item $key - captureType]]> $type - $useCdata $value - HeadScript + $this isHtml5 - isXhtml attributes['conditional']]]> @@ -462,10 +391,8 @@ $type - attributes]]> attributes]]> source]]> - source]]> type]]> @@ -475,14 +402,6 @@ $index - - null - - - $captureLock - $captureScriptType - $captureType - (bool) $flag @@ -492,16 +411,21 @@ plugin - plugin - - getIndent - getSeparator - getWhitespace - setSeparator - + + $item + + + content)]]> + content)]]> + content) || ! is_string($item->content)]]> + isValid($value)]]> + isValid($value)]]> + isValid($value)]]> + isValid($value)]]> + offsetSet @@ -509,6 +433,9 @@ $content $index + + $attrs + $content $index @@ -520,41 +447,14 @@ is_string($content) - - setSeparator - - - $item - $item - - - $item]]> - $item - $separator - - $items - - $indent $item - $items[] - $postfix - $prefix - $separator - - $indent - $postfix - $prefix - - - translate - @@ -645,19 +545,6 @@ (bool) $useNamespaces - - - parent::__invoke($mode, $spec, $placement, $attrs, $type) - - - InlineScript - - - InlineScript - InlineScript - InlineScript - - $response @@ -1237,18 +1124,6 @@ - - containerClass($value)]]> - - - items[$key]]]> - - - AbstractContainer - - - items]]> - (string) $key (string) $key @@ -1256,91 +1131,62 @@ (string) $key (string) $name - - - - Container - Container - + + containerClass($value)]]> + - self + $this + + $data + $data + $data + $data + nextIndex()]]> + nextIndex()]]> + $this[$key] .= $data + + + max($keys) + 1 + + + int + __toString - - AbstractContainer - $items + getArrayCopy()]]> + $values - - $this[$key] - - - $key - - - int - $this[$key] max($keys) - - max($keys) + 1 - - - $captureKey - $captureType - - (string) $indent (string) $postfix (string) $prefix (string) $separator - - captureKey]]> - - - containerClass()]]> - - - container]]> - __toString - - ArrayAccess - IteratorAggregate - - $container[$key] $return - - AbstractContainer - - - null - - - containerClass()]]> - (bool) $autoEscape (string) $string (string) $string - - container]]> - + + containerClass()]]> + @@ -2253,10 +2099,6 @@ - - - - @@ -2327,7 +2169,6 @@ getValue getValue getValue - setIndent @@ -2370,7 +2211,6 @@ type]]> - getArrayCopy getArrayCopy getArrayCopy getValue @@ -2379,7 +2219,6 @@ offsetSetName offsetSetName offsetSetName - setIndent @@ -2389,39 +2228,7 @@ - $item - $item - $item - source]]> - $values - $values - - $items[$i] - - - $item - $item - $items - $values - - - source]]> - - - getArrayCopy - getArrayCopy - setIndent - setIndent - - - - - $placeholder - - - renderTitle - @@ -2950,23 +2757,11 @@ translations]]> - - - Bar - Bar - - $value - - - MockContainer - MockContainer - - getDependencyConfig())]]> diff --git a/src/Helper/HeadLink.php b/src/Helper/HeadLink.php index 96c0f6a64..977ad7b28 100644 --- a/src/Helper/HeadLink.php +++ b/src/Helper/HeadLink.php @@ -5,17 +5,17 @@ namespace Laminas\View\Helper; use Laminas\View\Exception; -use stdClass; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; use function array_intersect; use function array_keys; use function array_shift; -use function call_user_func_array; use function count; -use function func_get_args; use function get_object_vars; use function implode; use function is_array; +use function is_object; use function is_string; use function method_exists; use function preg_match; @@ -24,11 +24,8 @@ use const PHP_EOL; -// @codingStandardsIgnoreStart /** - * Laminas_Layout_View_Helper_HeadLink - * - * @see http://www.w3.org/TR/xhtml1/dtds.html + * @extends AbstractStandalone * * Creates the following virtual methods: * @method HeadLink appendStylesheet($href, $media = 'screen', $conditionalStylesheet = '', $extras = []) @@ -40,8 +37,7 @@ * @method HeadLink prependAlternate($href, $type, $title, $extras = []) * @method HeadLink setAlternate($href, $type, $title, $extras = []) */ -class HeadLink extends Placeholder\Container\AbstractStandalone -// @codingStandardsIgnoreEnd +class HeadLink extends AbstractStandalone { /** * Allowed attributes @@ -84,13 +80,13 @@ public function __construct() * Allows calling $helper->headLink(), but, more importantly, chaining calls * like ->appendStylesheet()->headLink(). * - * @param array|null $attributes + * @param array|null $attributes * @param string $placement * @return HeadLink */ - public function headLink(?array $attributes = null, $placement = Placeholder\Container\AbstractContainer::APPEND) + public function headLink(?array $attributes = null, $placement = AbstractContainer::APPEND) { - return call_user_func_array([$this, '__invoke'], func_get_args()); + return $this->__invoke($attributes, $placement); } /** @@ -99,22 +95,22 @@ public function headLink(?array $attributes = null, $placement = Placeholder\Con * Returns current object instance. Optionally, allows passing array of * values to build link. * - * @param array|null $attributes - * @param string $placement - * @return HeadLink + * @param array|null $attributes + * @param string $placement + * @return $this */ - public function __invoke(?array $attributes = null, $placement = Placeholder\Container\AbstractContainer::APPEND) + public function __invoke(?array $attributes = null, $placement = AbstractContainer::APPEND) { if (null !== $attributes) { $item = $this->createData($attributes); switch ($placement) { - case Placeholder\Container\AbstractContainer::SET: + case AbstractContainer::SET: $this->set($item); break; - case Placeholder\Container\AbstractContainer::PREPEND: + case AbstractContainer::PREPEND: $this->prepend($item); break; - case Placeholder\Container\AbstractContainer::APPEND: + case AbstractContainer::APPEND: default: $this->append($item); break; @@ -201,12 +197,14 @@ public function __call($method, $args) /** * Check if value is valid * - * @param mixed $value + * @internal This method will become private in version 3.0 + * + * @param mixed $value * @return bool */ protected function isValid($value) { - if (! $value instanceof stdClass) { + if (! is_object($value)) { return false; } @@ -223,9 +221,9 @@ protected function isValid($value) /** * append() * - * @param mixed $value + * @param object $value * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function append($value) { @@ -241,8 +239,8 @@ public function append($value) /** * offsetSet() * - * @param string|int $index - * @param array $value + * @param int $index + * @param object $value * @throws Exception\InvalidArgumentException * @return void */ @@ -260,9 +258,9 @@ public function offsetSet($index, $value) /** * prepend() * - * @param mixed $value + * @param object $value * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function prepend($value) { @@ -278,9 +276,9 @@ public function prepend($value) /** * set() * - * @param array $value + * @param object $value * @throws Exception\InvalidArgumentException - * @return HeadLink + * @return $this */ public function set($value) { @@ -290,15 +288,19 @@ public function set($value) ); } - return $this->getContainer()->set($value); + $this->getContainer()->set($value); + + return $this; } /** * Create HTML link element from data item * + * @internal This method will become private in version 3.0 + * * @return string */ - public function itemToString(stdClass $item) + public function itemToString(object $item) { $attributes = (array) $item; $link = 'getWhitespace($indent) - : $this->getIndent(); + ? $this->getContainer()->getWhitespace($indent) + : $this->getContainer()->getIndent(); $items = []; $this->getContainer()->ksort(); @@ -366,14 +368,16 @@ public function toString($indent = null) $items[] = $this->itemToString($item); } - return $indent . implode($this->escape($this->getSeparator()) . $indent, $items); + return $indent . implode($this->escape($this->getContainer()->getSeparator()) . $indent, $items); } /** * Create data item for stack * - * @param array $attributes - * @return stdClass + * @internal This method will become private in version 3.0 + * + * @param array $attributes + * @return object */ public function createData(array $attributes) { @@ -383,8 +387,10 @@ public function createData(array $attributes) /** * Create item for stylesheet link item * + * @deprecated This method is unused and will be removed in version 3.0 of this component + * * @param array $args - * @return stdClass|false Returns false if stylesheet is a duplicate + * @return object|false Returns false if stylesheet is a duplicate */ public function createDataStylesheet(array $args) { @@ -435,6 +441,8 @@ public function createDataStylesheet(array $args) /** * Is the linked stylesheet a duplicate? * + * @internal This method will become private in version 3.0 + * * @param string $uri * @return bool */ @@ -452,9 +460,11 @@ protected function isDuplicateStylesheet($uri) /** * Create item for alternate link item * + * @deprecated This method is unused and will be removed in version 3.0 of this component + * * @param array $args * @throws Exception\InvalidArgumentException - * @return stdClass + * @return object */ public function createDataAlternate(array $args) { @@ -493,8 +503,10 @@ public function createDataAlternate(array $args) /** * Create item for a prev relationship (mainly used for pagination) * + * @deprecated This method is unused and will be removed in version 3.0 of this component + * * @param array $args - * @return stdClass + * @return object */ public function createDataPrev(array $args) { @@ -507,8 +519,10 @@ public function createDataPrev(array $args) /** * Create item for a prev relationship (mainly used for pagination) * + * @deprecated This method is unused and will be removed in version 3.0 of this component + * * @param array $args - * @return stdClass + * @return object */ public function createDataNext(array $args) { diff --git a/src/Helper/HeadMeta.php b/src/Helper/HeadMeta.php index 8e460b295..a0982ab89 100644 --- a/src/Helper/HeadMeta.php +++ b/src/Helper/HeadMeta.php @@ -6,6 +6,8 @@ use Laminas\View; use Laminas\View\Exception; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; use stdClass; use function array_shift; @@ -14,6 +16,7 @@ use function count; use function implode; use function in_array; +use function is_object; use function is_string; use function method_exists; use function preg_match; @@ -49,40 +52,49 @@ * @method HeadMeta offsetGetItemprop($index, $keyValue, $content, $modifiers = array()) * @method HeadMeta prependItemprop($keyValue, $content, $modifiers = array()) * @method HeadMeta setItemprop($keyValue, $content, $modifiers = array()) + * + * @psalm-type ObjectShape = object{ + * type: string, + * content: string, + * modifiers: array, + * name?: string, + * http-equiv?: string, + * charset?: string, + * property?: string, + * itemprop?: string, + * } + * @extends AbstractStandalone */ -class HeadMeta extends Placeholder\Container\AbstractStandalone +class HeadMeta extends AbstractStandalone { /** * Allowed key types * - * @var array + * @var list */ protected $typeKeys = ['name', 'http-equiv', 'charset', 'property', 'itemprop']; /** * Required attributes for meta tag * - * @var array + * @deprecated This property is unused and will be removed in version 3.0 + * + * @var list */ protected $requiredKeys = ['content']; /** * Allowed modifier keys * - * @var array + * @var list */ protected $modifierKeys = ['lang', 'scheme']; - /** - * Constructor - * - * Set separator to PHP_EOL - */ public function __construct() { parent::__construct(); - $this->setSeparator(PHP_EOL); + $this->getContainer()->setSeparator(PHP_EOL); } /** @@ -93,14 +105,14 @@ public function __construct() * @param string $keyType * @param array $modifiers * @param string $placement - * @return HeadMeta + * @return $this */ public function __invoke( $content = null, $keyValue = null, $keyType = 'name', $modifiers = [], - $placement = Placeholder\Container\AbstractContainer::APPEND + $placement = AbstractContainer::APPEND ) { if ((null !== $content) && (null !== $keyValue)) { $item = $this->createData($keyType, $keyValue, $content, $modifiers); @@ -126,7 +138,7 @@ public function __invoke( * @param string $method * @param array $args * @throws Exception\BadMethodCallException - * @return HeadMeta + * @return $this */ public function __call($method, $args) { @@ -162,7 +174,9 @@ public function __call($method, $args) $item = $this->createData($type, $args[0], $args[1], $args[2]); if ('offsetSet' === $action) { - return $this->offsetSet($index, $item); + $this->offsetSet($index, $item); + + return $this; } $this->$action($item); @@ -214,30 +228,33 @@ public function toString($indent = null) /** * Create data item for inserting into stack * + * @internal This method will become private in version 3.0 + * * @param string $type * @param string $typeValue * @param string $content * @param array $modifiers - * @return stdClass + * @return object */ public function createData($type, $typeValue, $content, array $modifiers) { - $data = new stdClass(); - $data->type = $type; - $data->$type = $typeValue; - $data->content = $content; - $data->modifiers = $modifiers; - - return $data; + return (object) [ + 'type' => $type, + $type => $typeValue, + 'content' => $content, + 'modifiers' => $modifiers, + ]; } /** * Build meta HTML string * + * @internal This method will become private in version 3.0 + * * @throws Exception\InvalidArgumentException * @return string */ - public function itemToString(stdClass $item) + public function itemToString(object $item) { if (! in_array($item->type, $this->typeKeys)) { throw new Exception\InvalidArgumentException(sprintf( @@ -312,6 +329,8 @@ public function itemToString(stdClass $item) /** * Normalize type attribute of meta * + * @internal This method will become private in version 3.0 + * * @param string $type type in CamelCase * @throws Exception\DomainException * @return string @@ -338,13 +357,16 @@ protected function normalizeType($type) /** * Determine if item is valid * - * @param stdClass $item + * @internal This method will become private in version 3.0 + * + * @param mixed $item * @return bool + * @psalm-param-out ObjectShape $item */ protected function isValid($item) { if ( - ! $item instanceof stdClass + ! is_object($item) || ! isset($item->type) || ! isset($item->modifiers) ) { @@ -386,7 +408,7 @@ protected function isValid($item) /** * Append * - * @param stdClass $value + * @param object $value * @return View\Helper\Placeholder\Container\AbstractContainer * @throws Exception\InvalidArgumentException */ @@ -404,8 +426,8 @@ public function append($value) /** * OffsetSet * - * @param string|int $index - * @param string $value + * @param int $index + * @param mixed $value * @return void * @throws Exception\InvalidArgumentException */ @@ -423,7 +445,7 @@ public function offsetSet($index, $value) /** * OffsetUnset * - * @param string|int $index + * @param int $index * @return void * @throws Exception\InvalidArgumentException */ @@ -439,9 +461,9 @@ public function offsetUnset($index) /** * Prepend * - * @param stdClass $value + * @param object $value * @throws Exception\InvalidArgumentException - * @return View\Helper\Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function prepend($value) { @@ -457,9 +479,9 @@ public function prepend($value) /** * Set * - * @param stdClass $value + * @param object $value * @throws Exception\InvalidArgumentException - * @return View\Helper\Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function set($value) { @@ -484,7 +506,7 @@ public function set($value) * * @param string $charset * @throws Exception\InvalidArgumentException - * @return static + * @return $this */ public function setCharset($charset) { diff --git a/src/Helper/HeadScript.php b/src/Helper/HeadScript.php index 880cf594b..b2123c3ac 100644 --- a/src/Helper/HeadScript.php +++ b/src/Helper/HeadScript.php @@ -5,14 +5,18 @@ namespace Laminas\View\Helper; use Laminas\View\Exception; -use stdClass; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; +use Laminas\View\Renderer\PhpRenderer; use function array_key_exists; use function array_shift; +use function assert; use function count; use function filter_var; use function implode; use function in_array; +use function is_object; use function is_string; use function ob_get_clean; use function ob_start; @@ -38,8 +42,14 @@ * @method HeadScript offsetSetScript($index, $src, $type = 'text/javascript', $attrs = []) * @method HeadScript prependScript($script, $type = 'text/javascript', $attrs = []) * @method HeadScript setScript($script, $type = 'text/javascript', $attrs = []) + * @psalm-type ObjectShape = object{ + * type: string, + * attributes: array, + * source: string|null, + * } + * @extends AbstractStandalone */ -class HeadScript extends Placeholder\Container\AbstractStandalone +class HeadScript extends AbstractStandalone { /** * Script type constants @@ -66,33 +76,33 @@ class HeadScript extends Placeholder\Container\AbstractStandalone * * @var bool */ - protected $captureLock; + protected $captureLock = false; /** * Capture type * - * @var string + * @var string|null */ protected $captureScriptType; /** * Capture attributes * - * @var null|array + * @var null|array */ protected $captureScriptAttrs; /** * Capture type (append, prepend, set) * - * @var string + * @var string|null */ protected $captureType; /** * Optional allowed attributes for script tag * - * @var array + * @var list */ protected $optionalAttributes = [ 'charset', @@ -111,7 +121,7 @@ class HeadScript extends Placeholder\Container\AbstractStandalone /** * Script attributes that behave as booleans * - * @var string[] + * @var list */ private array $booleanAttributes = [ 'nomodule', @@ -122,7 +132,9 @@ class HeadScript extends Placeholder\Container\AbstractStandalone /** * Required attributes for script tag * - * @var string + * @deprecated This property is unused and will be removed in version 3.0 of this component + * + * @var list */ protected $requiredAttributes = ['type']; @@ -134,16 +146,11 @@ class HeadScript extends Placeholder\Container\AbstractStandalone */ public $useCdata = false; - /** - * Constructor - * - * Set separator to PHP_EOL. - */ public function __construct() { parent::__construct(); - $this->setSeparator(PHP_EOL); + $this->getContainer()->setSeparator(PHP_EOL); } /** @@ -155,9 +162,9 @@ public function __construct() * @param string $mode Script or file * @param string $spec Script/url * @param string $placement Append, prepend, or set - * @param array $attrs Array of script attributes + * @param array $attrs Array of script attributes * @param string $type Script type and/or array of script attributes - * @return HeadScript + * @return $this */ public function __invoke( $mode = self::FILE, @@ -191,7 +198,7 @@ public function __invoke( * @param string $method Method to call * @param array $args Arguments of method * @throws Exception\BadMethodCallException If too few arguments or invalid method. - * @return HeadScript + * @return $this */ public function __call($method, $args) { @@ -266,11 +273,14 @@ public function __call($method, $args) public function toString($indent = null) { $indent = null !== $indent - ? $this->getWhitespace($indent) - : $this->getIndent(); + ? $this->getContainer()->getWhitespace($indent) + : $this->getContainer()->getIndent(); + + if ($this->view instanceof PhpRenderer) { + $doctype = $this->view->plugin('doctype'); + assert($doctype instanceof Doctype); - if ($this->view) { - $useCdata = $this->view->plugin('doctype')->isXhtml(); + $useCdata = $doctype->isXhtml(); } else { $useCdata = $this->useCdata; } @@ -294,14 +304,14 @@ public function toString($indent = null) /** * Start capture action * - * @param mixed $captureType Type of capture - * @param string $type Type of script - * @param array $attrs Attributes of capture + * @param string $captureType Type of capture + * @param string $type Type of script + * @param array $attrs Attributes of capture * @throws Exception\RuntimeException * @return void */ public function captureStart( - $captureType = Placeholder\Container\AbstractContainer::APPEND, + $captureType = AbstractContainer::APPEND, $type = self::DEFAULT_SCRIPT_TYPE, $attrs = [] ) { @@ -331,9 +341,9 @@ public function captureEnd() $this->captureLock = false; switch ($this->captureType) { - case Placeholder\Container\AbstractContainer::SET: - case Placeholder\Container\AbstractContainer::PREPEND: - case Placeholder\Container\AbstractContainer::APPEND: + case AbstractContainer::SET: + case AbstractContainer::PREPEND: + case AbstractContainer::APPEND: $action = strtolower($this->captureType) . 'Script'; break; default: @@ -347,19 +357,20 @@ public function captureEnd() /** * Create data item containing all necessary components of script * + * @internal This method will become private in version 3.0 + * * @param string $type Type of data - * @param array $attributes Attributes of data + * @param array $attributes Attributes of data * @param string $content Content of data - * @return stdClass + * @return ObjectShape */ public function createData($type, array $attributes, $content = null) { - $data = new stdClass(); - $data->type = $type; - $data->attributes = $attributes; - $data->source = $content; - - return $data; + return (object) [ + 'type' => $type, + 'attributes' => $attributes, + 'source' => $content, + ]; } /** @@ -386,13 +397,15 @@ protected function isDuplicate($file) /** * Is the script provided valid? * + * @internal This method will become private in version 3.0 + * * @param mixed $value Is the given script valid? * @return bool */ protected function isValid($value) { if ( - ! $value instanceof stdClass + ! is_object($value) || ! isset($value->type) || (! isset($value->source) && ! isset($value->attributes)) @@ -406,6 +419,8 @@ protected function isValid($value) /** * Create script HTML * + * @internal This method will become private in version 3.0 + * * @param mixed $item Item to convert * @param string $indent String to add before the item * @param string $escapeStart Starting sequence @@ -484,9 +499,9 @@ public function itemToString($item, $indent, $escapeStart, $escapeEnd) /** * Override append * - * @param string $value Append script or file + * @param ObjectShape $value Append script or file * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function append($value) { @@ -503,9 +518,9 @@ public function append($value) /** * Override prepend * - * @param string $value Prepend script or file + * @param ObjectShape $value Prepend script or file * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function prepend($value) { @@ -522,7 +537,7 @@ public function prepend($value) /** * Override set * - * @param string $value Set script or file + * @param ObjectShape $value Set script or file * @throws Exception\InvalidArgumentException * @return void */ @@ -540,8 +555,8 @@ public function set($value) /** * Override offsetSet * - * @param string|int $index Set script of file offset - * @param mixed $value + * @param int $index Set script of file offset + * @param ObjectShape $value * @throws Exception\InvalidArgumentException * @return void */ @@ -561,7 +576,7 @@ public function offsetSet($index, $value) * Set flag indicating if arbitrary attributes are allowed * * @param bool $flag Set flag - * @return HeadScript + * @return $this */ public function setAllowArbitraryAttributes($flag) { diff --git a/src/Helper/HeadStyle.php b/src/Helper/HeadStyle.php index a68b8ea6b..adc6f91ac 100644 --- a/src/Helper/HeadStyle.php +++ b/src/Helper/HeadStyle.php @@ -6,6 +6,8 @@ use Laminas\View; use Laminas\View\Exception; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; use stdClass; use function array_filter; @@ -28,17 +30,22 @@ use const PHP_EOL; /** - * Helper for setting and retrieving stylesheets + * Helper for adding inline CSS to the head in style tags * - * Allows the following method calls: + * @psalm-type ObjectShape = object{ + * attributes: array, + * content: string, + * } + * @extends AbstractStandalone * - * @method HeadStyle appendStyle($content, $attributes = array()) - * @method HeadStyle offsetSetStyle($index, $content, $attributes = array()) - * @method HeadStyle prependStyle($content, $attributes = array()) - * @method HeadStyle setStyle($content, $attributes = array()) + * Allows the following method calls: + * @method HeadStyle appendStyle(string $content, array $attributes = []) + * @method HeadStyle offsetSetStyle(int $index, string $content, array $attributes = []) + * @method HeadStyle prependStyle(string $content, array $attributes = []) + * @method HeadStyle setStyle(string $content, array $attributes = []) * @method HeadStyle setIndent(int|string $indent) */ -class HeadStyle extends Placeholder\Container\AbstractStandalone +class HeadStyle extends AbstractStandalone { /** * Allowed optional attributes @@ -93,7 +100,7 @@ public function __construct() { parent::__construct(); - $this->setSeparator(PHP_EOL); + $this->getContainer()->setSeparator(PHP_EOL); } /** @@ -106,17 +113,17 @@ public function __construct() * @param string|array $attributes Optional attributes to utilize * @return HeadStyle */ - public function __invoke($content = null, $placement = 'APPEND', $attributes = []) + public function __invoke($content = null, $placement = AbstractContainer::APPEND, $attributes = []) { if ((null !== $content) && is_string($content)) { switch (strtoupper($placement)) { - case 'SET': + case AbstractContainer::SET: $action = 'setStyle'; break; - case 'PREPEND': + case AbstractContainer::PREPEND: $action = 'prependStyle'; break; - case 'APPEND': + case AbstractContainer::APPEND: default: $action = 'appendStyle'; break; @@ -210,7 +217,7 @@ public function toString($indent = null) * @throws Exception\RuntimeException * @return void */ - public function captureStart($type = Placeholder\Container\AbstractContainer::APPEND, $attrs = null) + public function captureStart($type = AbstractContainer::APPEND, $attrs = null) { if ($this->captureLock) { throw new Exception\RuntimeException('Cannot nest headStyle captures'); @@ -230,18 +237,18 @@ public function captureStart($type = Placeholder\Container\AbstractContainer::AP public function captureEnd() { $content = ob_get_clean(); - $attrs = $this->captureAttrs; + $attrs = $this->captureAttrs ?? []; $this->captureAttrs = null; $this->captureLock = false; switch ($this->captureType) { - case Placeholder\Container\AbstractContainer::SET: + case AbstractContainer::SET: $this->setStyle($content, $attrs); break; - case Placeholder\Container\AbstractContainer::PREPEND: + case AbstractContainer::PREPEND: $this->prependStyle($content, $attrs); break; - case Placeholder\Container\AbstractContainer::APPEND: + case AbstractContainer::APPEND: default: $this->appendStyle($content, $attrs); break; @@ -254,8 +261,8 @@ public function captureEnd() * @internal This method is internal and will be made private in version 3.0 * * @param string $content - * @param array $attributes - * @return stdClass + * @param array $attributes + * @return ObjectShape */ public function createData($content, array $attributes) { @@ -263,11 +270,10 @@ public function createData($content, array $attributes) $attributes['media'] = 'screen'; } - $data = new stdClass(); - $data->content = $content; - $data->attributes = $attributes; - - return $data; + return (object) [ + 'content' => $content, + 'attributes' => $attributes, + ]; } /** @@ -275,11 +281,10 @@ public function createData($content, array $attributes) * * @internal This method is internal and will be made private in version 3.0 * - * @param mixed $value * @return bool - * @psalm-assert-if-true stdClass $value + * @psalm-assert-if-true ObjectShape $value */ - protected function isValid($value) + protected function isValid(mixed $value) { if (! $value instanceof stdClass || ! isset($value->content) || ! isset($value->attributes)) { return false; @@ -360,11 +365,11 @@ private function styleTagAttributesString(object $item): string * * @internal This method is internal and will be made private in version 3.0 * - * @param stdClass $item Item to render + * @param ObjectShape $item Item to render * @param string $indent Indentation to use * @return string */ - public function itemToString(stdClass $item, $indent) + public function itemToString(object $item, $indent) { if (! isset($item->content) || ! is_string($item->content) || $item->content === '') { return ''; @@ -396,9 +401,9 @@ public function itemToString(stdClass $item, $indent) /** * Override append to enforce style creation * - * @param mixed $value + * @param ObjectShape $value * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function append($value) { @@ -414,8 +419,8 @@ public function append($value) /** * Override offsetSet to enforce style creation * - * @param string|int $index - * @param mixed $value + * @param int $index + * @param ObjectShape $value * @throws Exception\InvalidArgumentException * @return void */ @@ -433,9 +438,9 @@ public function offsetSet($index, $value) /** * Override prepend to enforce style creation * - * @param mixed $value + * @param ObjectShape $value * @throws Exception\InvalidArgumentException - * @return Placeholder\Container\AbstractContainer + * @return AbstractContainer */ public function prepend($value) { @@ -451,7 +456,7 @@ public function prepend($value) /** * Override set to enforce style creation * - * @param mixed $value + * @param ObjectShape $value * @throws Exception\InvalidArgumentException * @return void */ diff --git a/src/Helper/HeadTitle.php b/src/Helper/HeadTitle.php index ccfce1934..61e8eff32 100644 --- a/src/Helper/HeadTitle.php +++ b/src/Helper/HeadTitle.php @@ -5,7 +5,10 @@ namespace Laminas\View\Helper; use Laminas\View\Exception; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; +use function assert; use function implode; use function in_array; @@ -13,8 +16,13 @@ * Helper for setting and retrieving title element for HTML head. * * Duck-types against Laminas\I18n\Translator\TranslatorAwareInterface. + * + * @extends AbstractStandalone + * @method HeadTitle set(string $string) + * @method HeadTitle prepend(string $string) + * @method HeadTitle append(string $string) */ -class HeadTitle extends Placeholder\Container\AbstractStandalone +class HeadTitle extends AbstractStandalone { use TranslatorAwareTrait; @@ -36,14 +44,14 @@ public function __invoke($title = null, $setType = null) { if (null === $setType) { $setType = $this->getDefaultAttachOrder() - ?? Placeholder\Container\AbstractContainer::APPEND; + ?? AbstractContainer::APPEND; } $title = (string) $title; if ($title !== '') { - if ($setType === Placeholder\Container\AbstractContainer::SET) { + if ($setType === AbstractContainer::SET) { $this->set($title); - } elseif ($setType === Placeholder\Container\AbstractContainer::PREPEND) { + } elseif ($setType === AbstractContainer::PREPEND) { $this->prepend($title); } else { $this->append($title); @@ -62,8 +70,8 @@ public function __invoke($title = null, $setType = null) public function toString($indent = null) { $indent = null !== $indent - ? $this->getWhitespace($indent) - : $this->getIndent(); + ? $this->getContainer()->getWhitespace($indent) + : $this->getContainer()->getIndent(); $output = $this->renderTitle(); @@ -84,24 +92,22 @@ public function renderTitle() $items[] = $itemCallback($item); } - $separator = $this->getSeparator(); + $separator = $this->getContainer()->getSeparator(); $output = ''; - $prefix = $this->getPrefix(); + $prefix = $this->getContainer()->getPrefix(); if ($prefix) { $output .= $prefix; } $output .= implode($separator, $items); - $postfix = $this->getPostfix(); + $postfix = $this->getContainer()->getPostfix(); if ($postfix) { $output .= $postfix; } - $output = $this->autoEscape ? $this->escape($output) : $output; - - return $output; + return $this->autoEscape ? $this->escape($output) : $output; } /** @@ -109,15 +115,15 @@ public function renderTitle() * * @param string $setType * @throws Exception\DomainException - * @return HeadTitle + * @return $this */ public function setDefaultAttachOrder($setType) { if ( ! in_array($setType, [ - Placeholder\Container\AbstractContainer::APPEND, - Placeholder\Container\AbstractContainer::SET, - Placeholder\Container\AbstractContainer::PREPEND, + AbstractContainer::APPEND, + AbstractContainer::SET, + AbstractContainer::PREPEND, ], true) ) { throw new Exception\DomainException( @@ -146,7 +152,7 @@ public function getDefaultAttachOrder() * callable that simply returns the provided item; otherwise, returns a * callable that returns a translation of the provided item. * - * @return callable + * @return callable(string): string */ private function getTitleItemCallback() { @@ -155,6 +161,7 @@ private function getTitleItemCallback() } $translator = $this->getTranslator(); + assert($translator !== null); $textDomain = $this->getTranslatorTextDomain(); return static fn($item) => $translator->translate($item, $textDomain); } diff --git a/src/Helper/InlineScript.php b/src/Helper/InlineScript.php index 124cc88b4..b8299e50c 100644 --- a/src/Helper/InlineScript.php +++ b/src/Helper/InlineScript.php @@ -16,12 +16,12 @@ class InlineScript extends HeadScript * Returns InlineScript helper object; optionally, allows specifying a * script or script file to include. * - * @param string $mode Script or file - * @param string $spec Script/url - * @param string $placement Append, prepend, or set - * @param array $attrs Array of script attributes - * @param string $type Script type and/or array of script attributes - * @return InlineScript + * @param string $mode Script or file + * @param string $spec Script/url + * @param string $placement Append, prepend, or set + * @param array $attrs Array of script attributes + * @param string $type Script type and/or array of script attributes + * @return $this */ public function __invoke( $mode = self::FILE, diff --git a/src/Helper/Placeholder.php b/src/Helper/Placeholder.php index d6713cca6..a747668c8 100644 --- a/src/Helper/Placeholder.php +++ b/src/Helper/Placeholder.php @@ -21,14 +21,14 @@ class Placeholder extends AbstractHelper /** * Placeholder items * - * @var AbstractContainer[] + * @var array */ protected $items = []; /** * Default container class * - * @var string + * @var class-string */ protected $containerClass = Container::class; @@ -47,8 +47,7 @@ public function __invoke($name = null) ); } - $name = (string) $name; - return $this->getContainer($name); + return $this->getContainer((string) $name); } /** diff --git a/src/Helper/Placeholder/Container.php b/src/Helper/Placeholder/Container.php index 09611c493..ebb171baf 100644 --- a/src/Helper/Placeholder/Container.php +++ b/src/Helper/Placeholder/Container.php @@ -4,9 +4,15 @@ namespace Laminas\View\Helper\Placeholder; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; + /** * Container for placeholder values + * + * @template TKey + * @template TValue + * @extends AbstractContainer */ -class Container extends Container\AbstractContainer +class Container extends AbstractContainer { } diff --git a/src/Helper/Placeholder/Container/AbstractContainer.php b/src/Helper/Placeholder/Container/AbstractContainer.php index be8bdcb6e..1341c84f4 100644 --- a/src/Helper/Placeholder/Container/AbstractContainer.php +++ b/src/Helper/Placeholder/Container/AbstractContainer.php @@ -6,7 +6,7 @@ use ArrayObject; use Laminas\View\Exception; -use ReturnTypeWillChange; // phpcs:ignore +use ReturnTypeWillChange; use function array_keys; use function array_shift; @@ -23,25 +23,29 @@ /** * Abstract class representing container for placeholder values + * + * @template TKey + * @template TValue + * @extends ArrayObject */ abstract class AbstractContainer extends ArrayObject { /** - * Whether or not to override all contents of placeholder + * Whether to override all contents of placeholder * * @const string */ public const SET = 'SET'; /** - * Whether or not to append contents to placeholder + * Whether to append contents to placeholder * * @const string */ public const APPEND = 'APPEND'; /** - * Whether or not to prepend contents to placeholder + * Whether to prepend contents to placeholder * * @const string */ @@ -50,7 +54,7 @@ abstract class AbstractContainer extends ArrayObject /** * Key to which to capture content * - * @var string + * @var TKey|null */ protected $captureKey; @@ -64,7 +68,7 @@ abstract class AbstractContainer extends ArrayObject /** * What type of capture (overwrite (set), append, prepend) to use * - * @var string + * @var string|null */ protected $captureType; @@ -98,6 +102,8 @@ abstract class AbstractContainer extends ArrayObject /** * Constructor - This is needed so that we can attach a class member as the ArrayObject container + * + * @final */ public function __construct() { @@ -145,7 +151,7 @@ public function toString($indent = null) * Start capturing content to push into placeholder * * @param string $type How to capture content into placeholder; append, prepend, or set - * @param mixed $key Key to which to capture content + * @param TKey $key Key to which to capture content * @throws Exception\RuntimeException If nested captures detected. * @return void */ @@ -159,8 +165,8 @@ public function captureStart($type = self::APPEND, $key = null) $this->captureLock = true; $this->captureType = $type; - if ((null !== $key) && is_scalar($key)) { - $this->captureKey = (string) $key; + if (is_scalar($key)) { + $this->captureKey = $key; } ob_start(); } @@ -173,11 +179,9 @@ public function captureStart($type = self::APPEND, $key = null) public function captureEnd() { $data = ob_get_clean(); - $key = null; + $key = $this->captureKey; $this->captureLock = false; - if (null !== $this->captureKey) { - $key = $this->captureKey; - } + switch ($this->captureType) { case self::SET: if (null !== $key) { @@ -214,13 +218,11 @@ public function captureEnd() /** * Get keys * - * @return array + * @return list */ public function getKeys() { - $array = $this->getArrayCopy(); - - return array_keys($array); + return array_keys($this->getArrayCopy()); } /** @@ -229,7 +231,7 @@ public function getKeys() * If single element registered, returns that element; otherwise, * serializes to array. * - * @return mixed + * @return TValue|array */ public function getValue() { @@ -254,14 +256,14 @@ public function getWhitespace($indent) $indent = str_repeat(' ', $indent); } - return (string) $indent; + return $indent; } /** * Set a single value * - * @param mixed $value - * @return static + * @param TValue $value + * @return $this */ public function set($value) { @@ -273,8 +275,8 @@ public function set($value) /** * Prepend a value to the top of the container * - * @param mixed $value - * @return self + * @param TValue $value + * @return $this */ public function prepend($value) { @@ -288,8 +290,8 @@ public function prepend($value) /** * Append a value to the end of the container * - * @param mixed $value - * @return self + * @param TValue $value + * @return $this */ #[ReturnTypeWillChange] public function append($value) @@ -318,7 +320,7 @@ public function nextIndex() * optionally, if a number is passed, it will be the number of spaces * * @param string|int $indent - * @return self + * @return $this */ public function setIndent($indent) { @@ -340,7 +342,7 @@ public function getIndent() * Set postfix for __toString() serialization * * @param string $postfix - * @return self + * @return $this */ public function setPostfix($postfix) { @@ -362,7 +364,7 @@ public function getPostfix() * Set prefix for __toString() serialization * * @param string $prefix - * @return self + * @return $this */ public function setPrefix($prefix) { @@ -386,7 +388,7 @@ public function getPrefix() * Used to implode elements in container * * @param string $separator - * @return self + * @return $this */ public function setSeparator($separator) { diff --git a/src/Helper/Placeholder/Container/AbstractStandalone.php b/src/Helper/Placeholder/Container/AbstractStandalone.php index 8a3d4fbd4..565ef6f78 100644 --- a/src/Helper/Placeholder/Container/AbstractStandalone.php +++ b/src/Helper/Placeholder/Container/AbstractStandalone.php @@ -12,7 +12,7 @@ use Laminas\View\Exception; use Laminas\View\Helper\AbstractHelper; use Laminas\View\Helper\Placeholder\Container; -use ReturnTypeWillChange; // phpcs:ignore +use ReturnTypeWillChange; use function call_user_func_array; use function class_exists; @@ -25,6 +25,19 @@ /** * Base class for targeted placeholder helpers + * + * @template TKey + * @template TValue + * @implements IteratorAggregate + * @implements ArrayAccess + * @method static setSeparator(string $separator) + * @method string getSeparator() + * @method static setIndent(int|string $indent) + * @method string getIndent() + * @method static setPrefix(string $prefix) + * @method string getPrefix() + * @method static setPostfix(string $postfix) + * @method string getPostfix() */ abstract class AbstractStandalone extends AbstractHelper implements IteratorAggregate, @@ -39,22 +52,19 @@ abstract class AbstractStandalone extends AbstractHelper implements */ protected $autoEscape = true; - /** @var AbstractContainer */ + /** @var AbstractContainer|null */ protected $container; /** * Default container class * - * @var string + * @var class-string */ protected $containerClass = Container::class; /** @var array */ protected $escapers = []; - /** - * Constructor - */ public function __construct() { $this->setContainer($this->getContainer()); @@ -88,8 +98,8 @@ public function __call($method, $args) /** * Overloading: set property value * - * @param string $key - * @param mixed $value + * @param TKey $key + * @param TValue $value * @return void */ public function __set($key, $value) @@ -101,8 +111,8 @@ public function __set($key, $value) /** * Overloading: retrieve property * - * @param string $key - * @return mixed + * @param TKey $key + * @return TValue|null */ public function __get($key) { @@ -115,7 +125,7 @@ public function __get($key) /** * Overloading: check if property is set * - * @param string $key + * @param TKey $key * @return bool */ public function __isset($key) @@ -127,7 +137,7 @@ public function __isset($key) /** * Overloading: unset property * - * @param string $key + * @param TKey $key * @return void */ public function __unset($key) @@ -184,7 +194,7 @@ protected function escapeAttribute($string) * Set whether or not auto escaping should be used * * @param bool $autoEscape whether or not to auto escape output - * @return AbstractStandalone + * @return $this */ public function setAutoEscape($autoEscape = true) { @@ -205,7 +215,7 @@ public function getAutoEscape() /** * Set container on which to operate * - * @return AbstractStandalone + * @return $this */ public function setContainer(AbstractContainer $container) { @@ -216,7 +226,7 @@ public function setContainer(AbstractContainer $container) /** * Retrieve placeholder container * - * @return AbstractContainer + * @return AbstractContainer */ public function getContainer() { @@ -244,10 +254,10 @@ public function deleteContainer() /** * Set the container class to use * - * @param string $name + * @param class-string $name * @throws Exception\InvalidArgumentException * @throws Exception\DomainException - * @return AbstractStandalone + * @return $this */ public function setContainerClass($name) { @@ -272,7 +282,7 @@ public function setContainerClass($name) /** * Retrieve the container class * - * @return string + * @return class-string */ public function getContainerClass() { @@ -325,7 +335,7 @@ public function count() /** * ArrayAccess: offsetExists * - * @param string|int $offset + * @param TKey $offset * @return bool */ #[ReturnTypeWillChange] @@ -337,8 +347,8 @@ public function offsetExists($offset) /** * ArrayAccess: offsetGet * - * @param string|int $offset - * @return mixed + * @param TKey $offset + * @return TValue */ #[ReturnTypeWillChange] public function offsetGet($offset) @@ -349,8 +359,8 @@ public function offsetGet($offset) /** * ArrayAccess: offsetSet * - * @param string|int $offset - * @param mixed $value + * @param TKey $offset + * @param TValue $value * @return void */ #[ReturnTypeWillChange] @@ -362,7 +372,7 @@ public function offsetSet($offset, $value) /** * ArrayAccess: offsetUnset * - * @param string|int $offset + * @param TKey $offset * @return void */ #[ReturnTypeWillChange] diff --git a/test/Helper/HeadLinkTest.php b/test/Helper/HeadLinkTest.php index f47ea7a3e..211573178 100644 --- a/test/Helper/HeadLinkTest.php +++ b/test/Helper/HeadLinkTest.php @@ -25,7 +25,6 @@ class HeadLinkTest extends TestCase { private HeadLink $helper; private EscapeHtmlAttr $attributeEscaper; - private string $basePath; private View $view; /** @@ -35,9 +34,8 @@ class HeadLinkTest extends TestCase protected function setUp(): void { Helper\Doctype::unsetDoctypeRegistry(); - $this->basePath = __DIR__ . '/_files/modules'; - $this->view = new View(); - $this->helper = new HeadLink(); + $this->view = new View(); + $this->helper = new HeadLink(); $this->helper->setView($this->view); $this->attributeEscaper = new EscapeHtmlAttr(); } @@ -51,24 +49,28 @@ public function testHeadLinkReturnsObjectInstance(): void public function testPrependThrowsExceptionWithoutArrayArgument(): void { $this->expectException(Exception\ExceptionInterface::class); + /** @psalm-suppress InvalidArgument */ $this->helper->prepend('foo'); } public function testAppendThrowsExceptionWithoutArrayArgument(): void { $this->expectException(Exception\ExceptionInterface::class); + /** @psalm-suppress InvalidArgument */ $this->helper->append('foo'); } public function testSetThrowsExceptionWithoutArrayArgument(): void { $this->expectException(Exception\ExceptionInterface::class); + /** @psalm-suppress InvalidArgument */ $this->helper->set('foo'); } public function testOffsetSetThrowsExceptionWithoutArrayArgument(): void { $this->expectException(Exception\ExceptionInterface::class); + /** @psalm-suppress InvalidArgument */ $this->helper->offsetSet(1, 'foo'); } diff --git a/test/Helper/HeadMetaTest.php b/test/Helper/HeadMetaTest.php index a0ff212fc..2646b883d 100644 --- a/test/Helper/HeadMetaTest.php +++ b/test/Helper/HeadMetaTest.php @@ -103,11 +103,9 @@ protected function executeOverloadAppend(string $type): void $this->assertCount($i + 1, $values); $item = $values[$i]; - self::assertIsObject($item); $this->assertObjectHasProperty('type', $item); $this->assertObjectHasProperty('modifiers', $item); $this->assertObjectHasProperty('content', $item); - self::assertIsString($item->type); $this->assertObjectHasProperty($item->type, $item); $this->assertEquals('keywords', $item->{$item->type}); $this->assertEquals($string, $item->content); @@ -128,7 +126,6 @@ protected function executeOverloadPrepend(string $type): void $this->assertObjectHasProperty('type', $item); $this->assertObjectHasProperty('modifiers', $item); $this->assertObjectHasProperty('content', $item); - self::assertIsString($item->type); $this->assertObjectHasProperty($item->type, $item); $this->assertEquals('keywords', $item->{$item->type}); $this->assertEquals($string, $item->content); @@ -146,8 +143,7 @@ protected function executeOverloadSet(string $type): void } $this->helper->$setAction('keywords', $string); - $values = $this->helper->getArrayCopy(); - self::assertIsArray($values); + $values = $this->helper->getContainer()->getArrayCopy(); $this->assertCount(1, $values); $item = array_shift($values); self::assertIsObject($item); @@ -155,7 +151,6 @@ protected function executeOverloadSet(string $type): void $this->assertObjectHasProperty('type', $item); $this->assertObjectHasProperty('modifiers', $item); $this->assertObjectHasProperty('content', $item); - self::assertIsString($item->type); $this->assertObjectHasProperty($item->type, $item); $this->assertEquals('keywords', $item->{$item->type}); $this->assertEquals($string, $item->content); diff --git a/test/Helper/HeadScriptTest.php b/test/Helper/HeadScriptTest.php index 46dbf0257..217f62823 100644 --- a/test/Helper/HeadScriptTest.php +++ b/test/Helper/HeadScriptTest.php @@ -7,14 +7,14 @@ use DOMDocument; use Generator; use Laminas\View; -use Laminas\View\Helper; use Laminas\View\Helper\Doctype; +use Laminas\View\Helper\EscapeHtmlAttr; +use Laminas\View\Helper\HeadScript; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function array_shift; use function assert; -use function count; use function sprintf; use function strtolower; use function substr_count; @@ -25,36 +25,33 @@ class HeadScriptTest extends TestCase { - /** @var Helper\HeadScript */ + /** @var HeadScript */ public $helper; - /** @var Helper\EscapeHtmlAttr */ + /** @var EscapeHtmlAttr */ public $attributeEscaper; - /** @var string */ - public $basePath; - /** * Sets up the fixture, for example, open a network connection. * This method is called before a test is executed. */ protected function setUp(): void { - $this->basePath = __DIR__ . '/_files/modules'; - $this->helper = new Helper\HeadScript(); - $this->attributeEscaper = new Helper\EscapeHtmlAttr(); + $this->helper = new HeadScript(); + $this->attributeEscaper = new EscapeHtmlAttr(); } public function testHeadScriptReturnsObjectInstance(): void { $placeholder = $this->helper->__invoke(); - $this->assertInstanceOf(Helper\HeadScript::class, $placeholder); + $this->assertInstanceOf(HeadScript::class, $placeholder); } public function testAppendThrowsExceptionWithInvalidArguments(): void { $this->expectException(View\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid argument passed to append'); + /** @psalm-suppress InvalidArgument */ $this->helper->append('foo'); } @@ -62,6 +59,7 @@ public function testPrependThrowsExceptionWithInvalidArguments(): void { $this->expectException(View\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid argument passed to prepend'); + /** @psalm-suppress InvalidArgument */ $this->helper->prepend('foo'); } @@ -69,6 +67,7 @@ public function testSetThrowsExceptionWithInvalidArguments(): void { $this->expectException(View\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid argument passed to set'); + /** @psalm-suppress InvalidArgument */ $this->helper->set('foo'); } @@ -76,6 +75,7 @@ public function testOffsetSetThrowsExceptionWithInvalidArguments(): void { $this->expectException(View\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid argument passed to offsetSet'); + /** @psalm-suppress InvalidArgument */ $this->helper->offsetSet(1, 'foo'); } @@ -94,9 +94,7 @@ private function executeOverloadAppend(string $type): void $values = $this->helper->getContainer()->getArrayCopy(); self::assertCount($i + 1, $values); $item = $values[$i]; - self::assertIsObject($item); if ('file' === $type) { - self::assertIsArray($item->attributes); self::assertEquals($string, $item->attributes['src']); } elseif ('script' === $type) { self::assertEquals($string, $item->source); @@ -117,7 +115,6 @@ private function executeOverloadPrepend(string $type): void $first = array_shift($values); self::assertIsObject($first); if ('file' === $type) { - self::assertIsArray($first->attributes); self::assertEquals($string, $first->attributes['src']); } elseif ('script' === $type) { self::assertEquals($string, $first->source); @@ -138,9 +135,7 @@ private function executeOverloadSet(string $type): void $values = $this->helper->getContainer()->getArrayCopy(); self::assertCount(1, $values); $item = $values[0]; - self::assertIsObject($item); if ('file' === $type) { - self::assertIsArray($item->attributes); self::assertEquals($string, $item->attributes['src']); } elseif ('script' === $type) { self::assertEquals($string, $item->source); @@ -156,9 +151,7 @@ private function executeOverloadOffsetSet(string $type): void $values = $this->helper->getContainer()->getArrayCopy(); self::assertCount(1, $values); $item = $values[5]; - self::assertIsObject($item); if ('file' === $type) { - self::assertIsArray($item->attributes); self::assertEquals($string, $item->attributes['src']); } elseif ('script' === $type) { self::assertEquals($string, $item->source); @@ -235,7 +228,7 @@ public function testHeadScriptAppropriatelySetsScriptItems(): void $this->helper->__invoke('FILE', 'foo', 'set') ->__invoke('SCRIPT', 'bar', 'prepend') ->__invoke('SCRIPT', 'baz', 'append'); - $items = $this->helper->getArrayCopy(); + $items = $this->helper->getContainer()->getArrayCopy(); for ($i = 0; $i < 3; ++$i) { $item = $items[$i]; switch ($i) { @@ -288,8 +281,8 @@ public function testCapturingCapturesToObject(): void $this->helper->captureStart(); echo 'foobar'; $this->helper->captureEnd(); - $values = $this->helper->getArrayCopy(); - $this->assertEquals(1, count($values), var_export($values, true)); + $values = $this->helper->getContainer()->getArrayCopy(); + $this->assertCount(1, $values, var_export($values, true)); $item = array_shift($values); $this->assertStringContainsString('foobar', $item->source); } @@ -317,7 +310,7 @@ public function testDoesNotAllowDuplicateFiles(): void { $this->helper->__invoke('FILE', '/js/prototype.js'); $this->helper->__invoke('FILE', '/js/prototype.js'); - $this->assertEquals(1, count($this->helper)); + $this->assertCount(1, $this->helper); } public function testRenderingDoesNotRenderArbitraryAttributesByDefault(): void diff --git a/test/Helper/HeadStyleTest.php b/test/Helper/HeadStyleTest.php index 5924f33b8..9646ea9f0 100644 --- a/test/Helper/HeadStyleTest.php +++ b/test/Helper/HeadStyleTest.php @@ -34,6 +34,7 @@ public function testAppendThrowsExceptionGivenNonStyleArgument(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid value passed to append'); + /** @psalm-suppress InvalidArgument */ $this->helper->append('foo'); } @@ -41,6 +42,7 @@ public function testPrependThrowsExceptionGivenNonStyleArgument(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid value passed to prepend'); + /** @psalm-suppress InvalidArgument */ $this->helper->prepend('foo'); } @@ -48,6 +50,7 @@ public function testSetThrowsExceptionGivenNonStyleArgument(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid value passed to set'); + /** @psalm-suppress InvalidArgument */ $this->helper->set('foo'); } @@ -55,6 +58,7 @@ public function testOffsetSetThrowsExceptionGivenNonStyleArgument(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid value passed to offsetSet'); + /** @psalm-suppress InvalidArgument */ $this->helper->offsetSet(1, 'foo'); } @@ -190,12 +194,6 @@ public function testHeadStyleProxiesProperly(): void ->__invoke($style3, 'APPEND'); self::assertCount(3, $this->helper); $values = $this->helper->getContainer()->getArrayCopy(); - self::assertIsObject($values[0]); - self::assertIsObject($values[1]); - self::assertIsObject($values[2]); - self::assertIsString($values[0]->content); - self::assertIsString($values[1]->content); - self::assertIsString($values[2]->content); self::assertStringContainsString($values[0]->content, $style2); self::assertStringContainsString($values[1]->content, $style1); self::assertStringContainsString($values[2]->content, $style3); @@ -235,7 +233,6 @@ public function testCapturingCapturesToObject(): void $item = array_shift($values); self::assertIsObject($item); self::assertObjectHasProperty('content', $item); - self::assertIsString($item->content); self::assertStringContainsString('foobar', $item->content); } @@ -246,9 +243,7 @@ public function testOverloadingOffsetSetWritesToSpecifiedIndex(): void self::assertCount(1, $values); self::assertTrue(isset($values[100])); $item = $values[100]; - self::assertIsObject($item); self::assertObjectHasProperty('content', $item); - self::assertIsString($item->content); self::assertStringContainsString('foobar', $item->content); } diff --git a/test/Helper/HeadTitleTest.php b/test/Helper/HeadTitleTest.php index 9b19feaf6..759f4841c 100644 --- a/test/Helper/HeadTitleTest.php +++ b/test/Helper/HeadTitleTest.php @@ -5,64 +5,55 @@ namespace LaminasTest\View\Helper; use Laminas\I18n\Translator\Translator; -use Laminas\View\Helper; +use Laminas\View\Helper\HeadTitle; use PHPUnit\Framework\TestCase; class HeadTitleTest extends TestCase { - /** @var Helper\HeadTitle */ + /** @var HeadTitle */ public $helper; - /** @var string */ - public $basePath; - - /** - * Sets up the fixture, for example, open a network connection. - * This method is called before a test is executed. - */ protected function setUp(): void { - $this->basePath = __DIR__ . '/_files/modules'; - $this->helper = new Helper\HeadTitle(); + $this->helper = new HeadTitle(); } - public function testHeadTitleReturnsObjectInstance(): void + public function testInvokeWithNoArgumentsReturnsSelf(): void { - $placeholder = $this->helper->__invoke(); - $this->assertInstanceOf(Helper\HeadTitle::class, $placeholder); + self::assertSame($this->helper, $this->helper->__invoke()); } public function testCanSetTitleViaHeadTitle(): void { $placeholder = $this->helper->__invoke('Foo Bar', 'SET'); - $this->assertEquals('Foo Bar', $placeholder->renderTitle()); + self::assertEquals('Foo Bar', $placeholder->renderTitle()); } public function testToStringWrapsToTitleTag(): void { $placeholder = $this->helper->__invoke('Foo Bar', 'SET'); - $this->assertEquals('Foo Bar', $placeholder->toString()); + self::assertEquals('Foo Bar', $placeholder->toString()); } public function testCanAppendTitleViaHeadTitle(): void { $this->helper->__invoke('Foo'); - $placeholder = $this->helper->__invoke('Bar'); - $this->assertEquals('FooBar', $placeholder->renderTitle()); + $this->helper->__invoke('Bar'); + self::assertEquals('FooBar', $this->helper->renderTitle()); } public function testCanPrependTitleViaHeadTitle(): void { $this->helper->__invoke('Foo'); $placeholder = $this->helper->__invoke('Bar', 'PREPEND'); - $this->assertEquals('BarFoo', $placeholder->renderTitle()); + self::assertEquals('BarFoo', $placeholder->renderTitle()); } public function testReturnedPlaceholderRenderTitleContainsFullTitleElement(): void { $this->helper->__invoke('Foo'); - $placeholder = $this->helper->__invoke('Bar', 'APPEND')->setSeparator(' :: '); - $this->assertEquals('Foo :: Bar', $placeholder->renderTitle()); + $this->helper->__invoke('Bar', 'APPEND')->setSeparator(' :: '); + self::assertEquals('Foo :: Bar', $this->helper->renderTitle()); } public function testRenderTitleEscapesEntries(): void @@ -82,7 +73,7 @@ public function testRenderTitleEscapesSeparator(): void $this->assertStringNotContainsString('
', $string); $this->assertStringContainsString('Foo', $string); $this->assertStringContainsString('Bar', $string); - $this->assertStringContainsString('br /', $string); + $this->assertStringContainsString('<br />', $string); } public function testIndentationIsHonored(): void @@ -106,7 +97,7 @@ public function testAutoEscapeIsHonored(): void $this->assertEquals('Some Title ©right;', $this->helper->renderTitle()); } - public function testLaminas918(): void + public function testThatAPrefixAndPostfixCanBeApplied(): void { $this->helper->__invoke('Some Title'); $this->helper->setPrefix('Prefix: '); @@ -115,7 +106,7 @@ public function testLaminas918(): void $this->assertEquals('Prefix: Some Title :Postfix', $this->helper->renderTitle()); } - public function testLaminas577(): void + public function testThatPrefixAndPostfixAreEscapedProperly(): void { $this->helper->setAutoEscape(true); $this->helper->__invoke('Some Title'); @@ -171,7 +162,17 @@ public function testCanPrependTitlesUsingDefaultAttachOrder(): void public function testReturnTypeDefaultAttachOrder(): void { - $this->assertInstanceOf(Helper\HeadTitle::class, $this->helper->setDefaultAttachOrder('PREPEND')); + $this->assertInstanceOf(HeadTitle::class, $this->helper->setDefaultAttachOrder('PREPEND')); $this->assertEquals('PREPEND', $this->helper->getDefaultAttachOrder()); } + + public function testCommonMagicMethods(): void + { + $this->helper->set('a little'); + $this->helper->prepend('Mary had'); + $this->helper->append('lamb'); + $this->helper->setSeparator(' '); + + self::assertSame('Mary had a little lamb', (string) $this->helper); + } } diff --git a/test/Helper/Placeholder/ContainerTest.php b/test/Helper/Placeholder/ContainerTest.php index 02468897d..0ee42055b 100644 --- a/test/Helper/Placeholder/ContainerTest.php +++ b/test/Helper/Placeholder/ContainerTest.php @@ -5,8 +5,10 @@ namespace LaminasTest\View\Helper\Placeholder; use Exception; -use Laminas\View\Helper\Placeholder\Container as PlaceholderContainer; +use Laminas\View\Helper\Placeholder\Container; +use LaminasTest\View\Helper\TestAsset\NonStringableObject; use PHPUnit\Framework\TestCase; +use Throwable; use function array_keys; use function array_pop; @@ -20,8 +22,8 @@ class ContainerTest extends TestCase { - /** @var PlaceholderContainer */ - public $container; + /** @var Container */ + private Container $container; /** * Sets up the fixture, for example, open a network connection. @@ -29,7 +31,9 @@ class ContainerTest extends TestCase */ protected function setUp(): void { - $this->container = new PlaceholderContainer(); + /** @var Container $container */ + $container = new Container(); + $this->container = $container; } public function testSetSetsASingleValue(): void @@ -136,16 +140,18 @@ public function testCapturingToPlaceholderStoresContent(): void $this->container->captureStart(); echo 'This is content intended for capture'; $this->container->captureEnd(); + $value = $this->container->getValue(); + self::assertIsString($value); $this->assertStringContainsString( 'This is content intended for capture', - (string) $this->container->getValue() + $value, ); } public function testCapturingToPlaceholderAppendsContent(): void { - $this->container[] = 'foo'; - $originalCount = count($this->container); + $this->container->append('foo'); + $originalCount = count($this->container); $this->container->captureStart(); echo 'This is content intended for capture'; @@ -161,14 +167,14 @@ public function testCapturingToPlaceholderAppendsContent(): void $this->assertEquals('foo', $value[$lastIndex - 1]); $this->assertStringContainsString( 'This is content intended for capture', - (string) $value[$lastIndex] + $value[$lastIndex] ); } public function testCapturingToPlaceholderUsingPrependPrependsContent(): void { - $this->container[] = 'foo'; - $originalCount = count($this->container); + $this->container->append('foo'); + $originalCount = count($this->container); $this->container->captureStart('PREPEND'); echo 'This is content intended for capture'; @@ -182,21 +188,23 @@ public function testCapturingToPlaceholderUsingPrependPrependsContent(): void $lastIndex = array_pop($keys); assert(is_int($lastIndex)); $this->assertEquals('foo', $value[$lastIndex]); - $this->assertStringContainsString('This is content intended for capture', (string) $value[$lastIndex - 1]); + $this->assertStringContainsString('This is content intended for capture', $value[$lastIndex - 1]); } public function testCapturingToPlaceholderUsingSetOverwritesContent(): void { - $this->container[] = 'foo'; + $this->container->append('foo'); $this->container->captureStart('SET'); echo 'This is content intended for capture'; $this->container->captureEnd(); $this->assertCount(1, $this->container); + $value = $this->container->getValue(); + self::assertIsString($value); $this->assertStringContainsString( 'This is content intended for capture', - (string) $this->container->getValue() + $value ); } @@ -210,7 +218,7 @@ public function testCapturingToPlaceholderKeyUsingSetCapturesContent(): void $this->assertTrue(isset($this->container['key'])); $this->assertStringContainsString( 'This is content intended for capture', - (string) $this->container['key'] + $this->container['key'] ); } @@ -223,7 +231,7 @@ public function testCapturingToPlaceholderKeyUsingSetReplacesContentAtKey(): voi $this->assertCount(1, $this->container); $this->assertTrue(isset($this->container['key'])); - $value = (string) $this->container['key']; + $value = $this->container['key']; $this->assertStringContainsString('This is content intended for capture', $value); } @@ -236,20 +244,20 @@ public function testCapturingToPlaceholderKeyUsingAppendAppendsContentAtKey(): v $this->assertCount(1, $this->container); $this->assertTrue(isset($this->container['key'])); - $value = (string) $this->container['key']; + $value = $this->container['key']; $this->assertStringContainsString('Foobar This is content intended for capture', $value); } public function testNestedCapturesThrowsException(): void { - $this->container[] = 'foo'; - $caught = false; + $this->container->append('foo'); + $caught = false; try { $this->container->captureStart('SET'); $this->container->captureStart('SET'); $this->container->captureEnd(); $this->container->captureEnd(); - } catch (Exception $e) { + } catch (Exception) { $this->container->captureEnd(); $caught = true; } @@ -275,18 +283,18 @@ public function testToStringWithModifiersAndSingleValueReturnsFormattedValue(): public function testToStringWithNoModifiersAndCollectionReturnsImplodedString(): void { - $this->container[] = 'foo'; - $this->container[] = 'bar'; - $this->container[] = 'baz'; - $value = $this->container->toString(); + $this->container->append('foo'); + $this->container->append('bar'); + $this->container->append('baz'); + $value = $this->container->toString(); $this->assertEquals('foobarbaz', $value); } public function testToStringWithModifiersAndCollectionReturnsFormattedString(): void { - $this->container[] = 'foo'; - $this->container[] = 'bar'; - $this->container[] = 'baz'; + $this->container->append('foo'); + $this->container->append('bar'); + $this->container->append('baz'); $this->container->setPrefix('
  • ') ->setSeparator('
  • ') ->setPostfix('
'); @@ -296,9 +304,9 @@ public function testToStringWithModifiersAndCollectionReturnsFormattedString(): public function testToStringWithModifiersAndCollectionReturnsFormattedStringWithIndentation(): void { - $this->container[] = 'foo'; - $this->container[] = 'bar'; - $this->container[] = 'baz'; + $this->container->append('foo'); + $this->container->append('bar'); + $this->container->append('baz'); $this->container->setPrefix('
  • ') ->setSeparator('
  • ' . PHP_EOL . '
  • ') ->setPostfix('
') @@ -310,9 +318,9 @@ public function testToStringWithModifiersAndCollectionReturnsFormattedStringWith public function testToStringProxiesToToString(): void { - $this->container[] = 'foo'; - $this->container[] = 'bar'; - $this->container[] = 'baz'; + $this->container->append('foo'); + $this->container->append('bar'); + $this->container->append('baz'); $this->container->setPrefix('
  • ') ->setSeparator('
  • ') ->setPostfix('
'); @@ -320,6 +328,18 @@ public function testToStringProxiesToToString(): void $this->assertEquals('
  • foo
  • bar
  • baz
', $value); } + public function testThatToStringBehaviourForNonStringableElementsWillCauseErrors(): void + { + $container = new Container(); + $container->append(new NonStringableObject()); + try { + $container->toString(); + self::fail('An exception was not thrown'); + } catch (Throwable $e) { + self::assertStringContainsString('could not be converted to string', $e->getMessage()); + } + } + public function testPrependPushesValueToTopOfContainer(): void { $this->container['foo'] = 'bar'; diff --git a/test/Helper/Placeholder/StandaloneContainerTest.php b/test/Helper/Placeholder/StandaloneContainerTest.php index ba410090b..0494c0339 100644 --- a/test/Helper/Placeholder/StandaloneContainerTest.php +++ b/test/Helper/Placeholder/StandaloneContainerTest.php @@ -7,6 +7,7 @@ use Laminas\View\Exception\DomainException; use Laminas\View\Exception\InvalidArgumentException; use Laminas\View\Helper\Placeholder\Container; +use Laminas\View\Helper\Placeholder\Container\AbstractContainer; use Laminas\View\Renderer\PhpRenderer as View; use LaminasTest\View\Helper\TestAsset\Bar; use LaminasTest\View\Helper\TestAsset\Foo; @@ -14,8 +15,7 @@ class StandaloneContainerTest extends TestCase { - /** @var Foo */ - protected $helper; + private Foo $helper; /** * Sets up the fixture, for example, open a network connection. @@ -37,14 +37,14 @@ public function testSetContainer(): void public function testGetContainer(): void { $container = $this->helper->getContainer(); - $this->assertInstanceOf(Container::class, $container); + $this->assertInstanceOf(AbstractContainer::class, $container); } public function testGetContainerCreatesNewContainer(): void { $this->helper->deleteContainer(); $container = $this->helper->getContainer(); - $this->assertInstanceOf(Container::class, $container); + $this->assertInstanceOf(AbstractContainer::class, $container); } public function testDeleteContainer(): void @@ -57,12 +57,14 @@ public function testDeleteContainer(): void public function testSetContainerClassThrowsDomainException(): void { $this->expectException(DomainException::class); - $this->helper->setContainerClass('bat'); + /** @psalm-suppress UndefinedClass, ArgumentTypeCoercion */ + $this->helper->setContainerClass('not-a-known-class'); } public function testSetContainerClassThrowsInvalidArgumentException(): void { $this->expectException(InvalidArgumentException::class); + /** @psalm-suppress InvalidArgument */ $this->helper->setContainerClass(static::class); } @@ -82,11 +84,11 @@ public function testViewAccessorWorks(): void public function testContainerDoesNotPersistBetweenInstances(): void { $foo1 = new Foo(); - $foo1->append('Foo'); + $foo1->getContainer()->append('Foo'); $foo1->setSeparator(' - '); $foo2 = new Foo(); - $foo2->append('Bar'); + $foo2->getContainer()->append('Bar'); $test = $foo2->toString(); $this->assertStringNotContainsString('Foo', $test); diff --git a/test/Helper/TestAsset/Bar.php b/test/Helper/TestAsset/Bar.php index 6a233f51a..599558f84 100644 --- a/test/Helper/TestAsset/Bar.php +++ b/test/Helper/TestAsset/Bar.php @@ -6,6 +6,7 @@ use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +/** @extends AbstractContainer */ class Bar extends AbstractContainer { } diff --git a/test/Helper/TestAsset/Foo.php b/test/Helper/TestAsset/Foo.php index c9a9332ba..24968fab6 100644 --- a/test/Helper/TestAsset/Foo.php +++ b/test/Helper/TestAsset/Foo.php @@ -6,6 +6,7 @@ use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; +/** @extends AbstractStandalone */ class Foo extends AbstractStandalone { } diff --git a/test/Helper/TestAsset/MockContainer.php b/test/Helper/TestAsset/MockContainer.php index 39819d984..e25bad837 100644 --- a/test/Helper/TestAsset/MockContainer.php +++ b/test/Helper/TestAsset/MockContainer.php @@ -5,7 +5,15 @@ namespace LaminasTest\View\Helper\TestAsset; use Laminas\View\Helper\Placeholder\Container\AbstractContainer; +use LaminasTest\View\Helper\Placeholder\RegistryTest; +/** + * @deprecated Should be removed in 3.0 when RegistryTest is removed + * + * @see RegistryTest + * + * @psalm-suppress MissingTemplateParam + */ class MockContainer extends AbstractContainer { /** @var array */ diff --git a/test/Helper/TestAsset/NonStringableObject.php b/test/Helper/TestAsset/NonStringableObject.php new file mode 100644 index 000000000..635c9ed43 --- /dev/null +++ b/test/Helper/TestAsset/NonStringableObject.php @@ -0,0 +1,9 @@ + Date: Wed, 19 Jul 2023 20:13:30 +0100 Subject: [PATCH 2/4] Reduce calls to $this->getContainer() in favour of a local variable. Also further type inference improvements and psalm fixes Signed-off-by: George Steel --- psalm-baseline.xml | 26 -------------------------- src/Helper/HeadLink.php | 13 +++++++------ src/Helper/HeadMeta.php | 28 ++++++++++++++-------------- src/Helper/HeadScript.php | 15 ++++++++------- src/Helper/HeadStyle.php | 14 +++++++------- src/Helper/HeadTitle.php | 16 +++++++++------- 6 files changed, 45 insertions(+), 67 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 01fd07451..324c4565b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -289,7 +289,6 @@ $args[1] $args[2] $index - $item $type]]> content]]> type]]> @@ -303,9 +302,7 @@ $doctype - $indent $index - $item $key $type $value @@ -321,10 +318,6 @@ isRdfa isXhtml - - $indent - $indent - parent::__call($method, $args) parent::__call($method, $args) @@ -346,9 +339,6 @@ plugin plugin - - getWhitespace -
@@ -374,7 +364,6 @@ $content $index - $item $key $type $value @@ -414,17 +403,10 @@ - - $item - content)]]> content)]]> content) || ! is_string($item->content)]]> - isValid($value)]]> - isValid($value)]]> - isValid($value)]]> - isValid($value)]]> offsetSet @@ -448,14 +430,6 @@ is_string($content) - - - $item - - - $item - - $htmlObject diff --git a/src/Helper/HeadLink.php b/src/Helper/HeadLink.php index 977ad7b28..3baea603e 100644 --- a/src/Helper/HeadLink.php +++ b/src/Helper/HeadLink.php @@ -358,17 +358,18 @@ public function itemToString(object $item) */ public function toString($indent = null) { - $indent = null !== $indent - ? $this->getContainer()->getWhitespace($indent) - : $this->getContainer()->getIndent(); + $container = $this->getContainer(); + $indent = null !== $indent + ? $container->getWhitespace($indent) + : $container->getIndent(); $items = []; - $this->getContainer()->ksort(); - foreach ($this as $item) { + $container->ksort(); + foreach ($container as $item) { $items[] = $this->itemToString($item); } - return $indent . implode($this->escape($this->getContainer()->getSeparator()) . $indent, $items); + return $indent . implode($this->escape($container->getSeparator()) . $indent, $items); } /** diff --git a/src/Helper/HeadMeta.php b/src/Helper/HeadMeta.php index a0982ab89..551cbdea7 100644 --- a/src/Helper/HeadMeta.php +++ b/src/Helper/HeadMeta.php @@ -8,7 +8,6 @@ use Laminas\View\Exception; use Laminas\View\Helper\Placeholder\Container\AbstractContainer; use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; -use stdClass; use function array_shift; use function array_unshift; @@ -195,19 +194,20 @@ public function __call($method, $args) */ public function toString($indent = null) { - $indent = null !== $indent - ? $this->getWhitespace($indent) - : $this->getIndent(); + $container = $this->getContainer(); + $indent = null !== $indent + ? $container->getWhitespace($indent) + : $container->getIndent(); $items = []; - $this->getContainer()->ksort(); + $container->ksort(); $doctype = $this->view->plugin('doctype'); assert($doctype instanceof Doctype); $isHtml5 = $doctype->isHtml5(); try { - foreach ($this as $item) { + foreach ($container as $item) { $content = $this->itemToString($item); if ($isHtml5 && $item->type === 'charset') { @@ -222,7 +222,7 @@ public function toString($indent = null) return ''; } - return $indent . implode($this->escape($this->getSeparator()) . $indent, $items); + return $indent . implode($this->escape($container->getSeparator()) . $indent, $items); } /** @@ -361,7 +361,6 @@ protected function normalizeType($type) * * @param mixed $item * @return bool - * @psalm-param-out ObjectShape $item */ protected function isValid($item) { @@ -461,7 +460,7 @@ public function offsetUnset($index) /** * Prepend * - * @param object $value + * @param object $value * @throws Exception\InvalidArgumentException * @return AbstractContainer */ @@ -510,11 +509,12 @@ public function set($value) */ public function setCharset($charset) { - $item = new stdClass(); - $item->type = 'charset'; - $item->charset = $charset; - $item->content = null; - $item->modifiers = []; + $item = (object) [ + 'type' => 'charset', + 'charset' => $charset, + 'content' => null, + 'modifiers' => [], + ]; if (! $this->isValid($item)) { throw new Exception\InvalidArgumentException( diff --git a/src/Helper/HeadScript.php b/src/Helper/HeadScript.php index b2123c3ac..085f01481 100644 --- a/src/Helper/HeadScript.php +++ b/src/Helper/HeadScript.php @@ -272,9 +272,10 @@ public function __call($method, $args) */ public function toString($indent = null) { - $indent = null !== $indent - ? $this->getContainer()->getWhitespace($indent) - : $this->getContainer()->getIndent(); + $container = $this->getContainer(); + $indent = null !== $indent + ? $container->getWhitespace($indent) + : $container->getIndent(); if ($this->view instanceof PhpRenderer) { $doctype = $this->view->plugin('doctype'); @@ -289,8 +290,8 @@ public function toString($indent = null) $escapeEnd = $useCdata ? '//]]>' : '//-->'; $items = []; - $this->getContainer()->ksort(); - foreach ($this as $item) { + $container->ksort(); + foreach ($container as $item) { if (! $this->isValid($item)) { continue; } @@ -298,7 +299,7 @@ public function toString($indent = null) $items[] = $this->itemToString($item, $indent, $escapeStart, $escapeEnd); } - return implode($this->getSeparator(), $items); + return implode($container->getSeparator(), $items); } /** @@ -399,7 +400,7 @@ protected function isDuplicate($file) * * @internal This method will become private in version 3.0 * - * @param mixed $value Is the given script valid? + * @param mixed $value Is the given script valid? * @return bool */ protected function isValid($value) diff --git a/src/Helper/HeadStyle.php b/src/Helper/HeadStyle.php index adc6f91ac..9bc3df0a0 100644 --- a/src/Helper/HeadStyle.php +++ b/src/Helper/HeadStyle.php @@ -191,20 +191,21 @@ public function __call($method, $args) */ public function toString($indent = null) { - $indent = null !== $indent - ? $this->getContainer()->getWhitespace($indent) - : $this->getContainer()->getIndent(); + $container = $this->getContainer(); + $indent = null !== $indent + ? $container->getWhitespace($indent) + : $container->getIndent(); $items = []; - $this->getContainer()->ksort(); - foreach ($this as $item) { + $container->ksort(); + foreach ($container as $item) { if (! $this->isValid($item)) { continue; } $items[] = $this->itemToString($item, $indent); } - $return = implode($this->getContainer()->getSeparator(), $items); + $return = implode($container->getSeparator(), $items); return $indent . preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return); } @@ -282,7 +283,6 @@ public function createData($content, array $attributes) * @internal This method is internal and will be made private in version 3.0 * * @return bool - * @psalm-assert-if-true ObjectShape $value */ protected function isValid(mixed $value) { diff --git a/src/Helper/HeadTitle.php b/src/Helper/HeadTitle.php index 61e8eff32..2547ddc12 100644 --- a/src/Helper/HeadTitle.php +++ b/src/Helper/HeadTitle.php @@ -69,9 +69,10 @@ public function __invoke($title = null, $setType = null) */ public function toString($indent = null) { - $indent = null !== $indent - ? $this->getContainer()->getWhitespace($indent) - : $this->getContainer()->getIndent(); + $container = $this->getContainer(); + $indent = null !== $indent + ? $container->getWhitespace($indent) + : $container->getIndent(); $output = $this->renderTitle(); @@ -88,21 +89,22 @@ public function renderTitle() $items = []; $itemCallback = $this->getTitleItemCallback(); - foreach ($this as $item) { + $container = $this->getContainer(); + foreach ($container as $item) { $items[] = $itemCallback($item); } - $separator = $this->getContainer()->getSeparator(); + $separator = $container->getSeparator(); $output = ''; - $prefix = $this->getContainer()->getPrefix(); + $prefix = $container->getPrefix(); if ($prefix) { $output .= $prefix; } $output .= implode($separator, $items); - $postfix = $this->getContainer()->getPostfix(); + $postfix = $container->getPostfix(); if ($postfix) { $output .= $postfix; } From 4a2ba1a21dfe130b55124e855c636723fc62be86 Mon Sep 17 00:00:00 2001 From: George Steel Date: Wed, 19 Jul 2023 21:04:15 +0100 Subject: [PATCH 3/4] Reduce psalm issues in tests Signed-off-by: George Steel --- psalm-baseline.xml | 45 ------------ test/Helper/HeadMetaTest.php | 123 +++++++++++++++++---------------- test/Helper/HeadScriptTest.php | 29 +++----- 3 files changed, 72 insertions(+), 125 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 324c4565b..0afc9c950 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2150,44 +2150,8 @@ - - - - - - - $modifiers - $value - $values - $values - $values - $values - - - $item - $item - $modifiers - $value - $values - $values - - - - - - - content]]> - content]]> - name]]> - name]]> - type]]> - type]]> - - getArrayCopy - getArrayCopy - getValue offsetSetHttpEquiv offsetSetHttpEquiv offsetSetName @@ -2195,15 +2159,6 @@ offsetSetName - - - - - - - - - HtmlFlash diff --git a/test/Helper/HeadMetaTest.php b/test/Helper/HeadMetaTest.php index 2646b883d..b47cb7527 100644 --- a/test/Helper/HeadMetaTest.php +++ b/test/Helper/HeadMetaTest.php @@ -4,30 +4,30 @@ namespace LaminasTest\View\Helper; +use Laminas\Escaper\Escaper; use Laminas\View\Exception; use Laminas\View\Exception\ExceptionInterface as ViewException; use Laminas\View\Helper; use Laminas\View\Helper\Doctype; -use Laminas\View\Helper\EscapeHtmlAttr; use Laminas\View\Helper\HeadMeta; use Laminas\View\Renderer\PhpRenderer as View; use PHPUnit\Framework\TestCase; use function array_shift; -use function count; +use function restore_error_handler; use function set_error_handler; use function sprintf; use function str_replace; use function substr_count; use function ucwords; +use const E_USER_WARNING; use const PHP_EOL; class HeadMetaTest extends TestCase { private HeadMeta $helper; - private EscapeHtmlAttr $attributeEscaper; - private ?string $error = null; + private Escaper $escaper; private View $view; /** @@ -42,12 +42,7 @@ protected function setUp(): void $doctype->__invoke('XHTML1_STRICT'); $this->helper = new HeadMeta(); $this->helper->setView($this->view); - $this->attributeEscaper = new EscapeHtmlAttr(); - } - - public function handleErrors(int $errno, string $errstr): void - { - $this->error = $errstr; + $this->escaper = new Escaper(); } public function testHeadMetaReturnsObjectInstance(): void @@ -203,8 +198,8 @@ public function testOverloadingThrowsExceptionWithInvalidMethodType(): void public function testCanBuildMetaTagsWithAttributes(): void { $this->helper->setName('keywords', 'foo bar', ['lang' => 'us_en', 'scheme' => 'foo', 'bogus' => 'unused']); - $value = $this->helper->getValue(); - + $value = $this->helper->getContainer()->getValue(); + self::assertIsObject($value); $this->assertObjectHasProperty('modifiers', $value); $modifiers = $value->modifiers; $this->assertArrayHasKey('lang', $modifiers); @@ -229,31 +224,45 @@ public function testToStringReturnsValidHtml(): void $metas = substr_count($string, 'http-equiv="'); $this->assertEquals(1, $metas); - $attributeEscaper = $this->attributeEscaper; - $this->assertStringContainsString('http-equiv="screen" content="projection"', $string); - $this->assertStringContainsString('name="keywords" content="' . $attributeEscaper('foo bar') . '"', $string); + $this->assertStringContainsString( + 'name="keywords" content="' . $this->escaper->escapeHtmlAttr('foo bar') . '"', + $string, + ); $this->assertStringContainsString('lang="us_en"', $string); $this->assertStringContainsString('scheme="foo"', $string); $this->assertStringNotContainsString('bogus', $string); $this->assertStringNotContainsString('unused', $string); - $this->assertStringContainsString('name="title" content="' . $attributeEscaper('boo bah') . '"', $string); + $this->assertStringContainsString( + 'name="title" content="' . $this->escaper->escapeHtmlAttr('boo bah') . '"', + $string, + ); } public function testToStringWhenInvalidKeyProvidedShouldConvertThrownException(): void { $this->helper->__invoke('some-content', 'tag value', 'not allowed key'); - set_error_handler([$this, 'handleErrors']); - $string = @$this->helper->toString(); - $this->assertEquals('', $string); - $this->assertIsString($this->error); + $error = null; + set_error_handler(function (int $code, string $message) use (&$error): bool { + self::assertSame(E_USER_WARNING, $code); + $error = $message; + + return true; + }, E_USER_WARNING); + $string = $this->helper->toString(); + self::assertEquals('', $string); + self::assertEquals( + 'Invalid type "not allowed key" provided for meta', + $error, + ); + restore_error_handler(); } public function testHeadMetaHelperCreatesItemEntry(): void { $this->helper->__invoke('foo', 'keywords'); - $values = $this->helper->getArrayCopy(); - $this->assertEquals(1, count($values)); + $values = $this->helper->getContainer()->getArrayCopy(); + $this->assertCount(1, $values); $item = array_shift($values); $this->assertEquals('foo', $item->content); $this->assertEquals('name', $item->type); @@ -263,8 +272,8 @@ public function testHeadMetaHelperCreatesItemEntry(): void public function testOverloadingOffsetInsertsAtOffset(): void { $this->helper->offsetSetName(100, 'keywords', 'foo'); - $values = $this->helper->getArrayCopy(); - $this->assertEquals(1, count($values)); + $values = $this->helper->getContainer()->getArrayCopy(); + $this->assertCount(1, $values); $this->assertArrayHasKey(100, $values); $item = $values[100]; $this->assertEquals('foo', $item->content); @@ -290,36 +299,32 @@ public function testStringRepresentationReflectsDoctype(): void $test = $this->helper->toString(); - $attributeEscaper = $this->attributeEscaper; - $this->assertStringNotContainsString('/>', $test); - $this->assertStringContainsString($attributeEscaper('some content'), $test); + $this->assertStringContainsString($this->escaper->escapeHtmlAttr('some content'), $test); $this->assertStringContainsString('foo', $test); } public function testSetNameDoesntClobber(): void { - $view = new View(); - $view->plugin(HeadMeta::class)->setName('keywords', 'foo'); - $view->plugin(HeadMeta::class)->appendHttpEquiv('pragma', 'bar'); - $view->plugin(HeadMeta::class)->appendHttpEquiv('Cache-control', 'baz'); - $view->plugin(HeadMeta::class)->setName('keywords', 'bat'); + $this->helper->setName('keywords', 'foo'); + $this->helper->appendHttpEquiv('pragma', 'bar'); + $this->helper->appendHttpEquiv('Cache-control', 'baz'); + $this->helper->setName('keywords', 'bat'); $this->assertEquals( '' . PHP_EOL . '' . PHP_EOL . '', - $view->plugin(HeadMeta::class)->toString() + $this->helper->toString() ); } public function testSetNameDoesntClobberPart2(): void { - $view = new View(); - $view->plugin(HeadMeta::class)->setName('keywords', 'foo'); - $view->plugin(HeadMeta::class)->setName('description', 'foo'); - $view->plugin(HeadMeta::class)->appendHttpEquiv('pragma', 'baz'); - $view->plugin(HeadMeta::class)->appendHttpEquiv('Cache-control', 'baz'); - $view->plugin(HeadMeta::class)->setName('keywords', 'bar'); + $this->helper->setName('keywords', 'foo'); + $this->helper->setName('description', 'foo'); + $this->helper->appendHttpEquiv('pragma', 'baz'); + $this->helper->appendHttpEquiv('Cache-control', 'baz'); + $this->helper->setName('keywords', 'bar'); $expected = sprintf( '%1$s' @@ -329,14 +334,13 @@ public function testSetNameDoesntClobberPart2(): void PHP_EOL ); - $this->assertEquals($expected, $view->plugin(HeadMeta::class)->toString()); + $this->assertEquals($expected, $this->helper->toString()); } public function testPlacesMetaTagsInProperOrder(): void { - $view = new View(); - $view->plugin(HeadMeta::class)->setName('keywords', 'foo'); - $view->plugin(HeadMeta::class)->__invoke( + $this->helper->setName('keywords', 'foo'); + $this->helper->__invoke( 'some content', 'bar', 'name', @@ -344,15 +348,13 @@ public function testPlacesMetaTagsInProperOrder(): void Helper\Placeholder\Container\AbstractContainer::PREPEND ); - $attributeEscaper = $this->attributeEscaper; - $expected = sprintf( '%s' . '', - $attributeEscaper('some content'), + $this->escaper->escapeHtmlAttr('some content'), PHP_EOL ); - $this->assertEquals($expected, $view->plugin(HeadMeta::class)->toString()); + $this->assertEquals($expected, $this->helper->toString()); } public function testContainerMaintainsCorrectOrderOfItems(): void @@ -381,7 +383,7 @@ public function testCharsetValidateFail(): void $view->plugin(Doctype::class)->__invoke('HTML4_STRICT'); $this->expectException(Exception\ExceptionInterface::class); - $view->plugin(HeadMeta::class)->setCharset('utf-8'); + $this->helper->setCharset('utf-8'); } public function testCharset(): void @@ -389,17 +391,17 @@ public function testCharset(): void $view = new View(); $view->plugin(Doctype::class)->__invoke('HTML5'); - $view->plugin(HeadMeta::class)->setCharset('utf-8'); + $this->helper->setCharset('utf-8'); $this->assertEquals( '', - $view->plugin(HeadMeta::class)->toString() + $this->helper->toString() ); $view->plugin(Doctype::class)->__invoke('XHTML5'); $this->assertEquals( '', - $view->plugin(HeadMeta::class)->toString() + $this->helper->toString() ); } @@ -408,18 +410,18 @@ public function testCharsetPosition(): void $view = new View(); $view->plugin(Doctype::class)->__invoke('HTML5'); - $view->plugin(HeadMeta::class) + $this->helper ->setProperty('description', 'foobar') ->setCharset('utf-8'); $this->assertEquals( '' . PHP_EOL . '', - $view->plugin(HeadMeta::class)->toString() + $this->helper->toString() ); } - public function testCarsetWithXhtmlDoctypeGotException(): void + public function testCharsetWithXhtmlDoctypeGotException(): void { $this->expectException(Exception\InvalidArgumentException::class); $this->expectExceptionMessage('XHTML* doctype has no attribute charset; please use appendHttpEquiv()'); @@ -427,7 +429,7 @@ public function testCarsetWithXhtmlDoctypeGotException(): void $view = new View(); $view->plugin(Doctype::class)->__invoke('XHTML1_RDFA'); - $view->plugin(HeadMeta::class) + $this->helper ->setCharset('utf-8'); } @@ -436,9 +438,7 @@ public function testPropertyIsSupportedWithRdfaDoctype(): void $this->view->doctype('XHTML1_RDFA'); $this->helper->__invoke('foo', 'og:title', 'property'); - $attributeEscaper = $this->attributeEscaper; - - $expected = sprintf('', $attributeEscaper('og:title')); + $expected = sprintf('', $this->escaper->escapeHtmlAttr('og:title')); $this->assertEquals($expected, $this->helper->toString()); } @@ -484,9 +484,10 @@ public function testItempropIsSupportedWithHtml5Doctype(): void $this->view->doctype('HTML5'); $this->helper->__invoke('HeadMeta with Microdata', 'description', 'itemprop'); - $attributeEscaper = $this->attributeEscaper; - - $expected = sprintf('', $attributeEscaper('HeadMeta with Microdata')); + $expected = sprintf( + '', + $this->escaper->escapeHtmlAttr('HeadMeta with Microdata'), + ); $this->assertEquals($expected, $this->helper->toString()); } diff --git a/test/Helper/HeadScriptTest.php b/test/Helper/HeadScriptTest.php index 217f62823..40af08bb8 100644 --- a/test/Helper/HeadScriptTest.php +++ b/test/Helper/HeadScriptTest.php @@ -6,9 +6,9 @@ use DOMDocument; use Generator; +use Laminas\Escaper\Escaper; use Laminas\View; use Laminas\View\Helper\Doctype; -use Laminas\View\Helper\EscapeHtmlAttr; use Laminas\View\Helper\HeadScript; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -25,20 +25,13 @@ class HeadScriptTest extends TestCase { - /** @var HeadScript */ - public $helper; + private HeadScript $helper; + private Escaper $escaper; - /** @var EscapeHtmlAttr */ - public $attributeEscaper; - - /** - * Sets up the fixture, for example, open a network connection. - * This method is called before a test is executed. - */ protected function setUp(): void { - $this->helper = new HeadScript(); - $this->attributeEscaper = new EscapeHtmlAttr(); + $this->helper = new HeadScript(); + $this->escaper = new Escaper(); } public function testHeadScriptReturnsObjectInstance(): void @@ -417,19 +410,17 @@ public function testContainerMaintainsCorrectOrderOfItems(): void $test = $this->helper->toString(); - $attributeEscaper = $this->attributeEscaper; - $expected = sprintf( '%1$s' . '%1$s' . '%1$s' . '', PHP_EOL, - $attributeEscaper('text/javascript'), - $attributeEscaper('test1.js'), - $attributeEscaper('test4.js'), - $attributeEscaper('test3.js'), - $attributeEscaper('test2.js') + $this->escaper->escapeHtmlAttr('text/javascript'), + $this->escaper->escapeHtmlAttr('test1.js'), + $this->escaper->escapeHtmlAttr('test4.js'), + $this->escaper->escapeHtmlAttr('test3.js'), + $this->escaper->escapeHtmlAttr('test2.js') ); $this->assertEquals($expected, $test); From 4364ebd5748b51f4a6e56254fb8cf97e271e215a Mon Sep 17 00:00:00 2001 From: George Steel Date: Thu, 20 Jul 2023 09:40:22 +0100 Subject: [PATCH 4/4] Revert BC breaking parameter type changes and mark helpers `@final` where possible Signed-off-by: George Steel --- psalm-baseline.xml | 25 +++++++------------------ src/Helper/HeadLink.php | 4 +++- src/Helper/HeadMeta.php | 4 +++- src/Helper/HeadScript.php | 8 ++++---- src/Helper/HeadStyle.php | 3 ++- src/Helper/HeadTitle.php | 1 + src/Helper/InlineScript.php | 2 ++ 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 0afc9c950..0acb49a4f 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -225,6 +225,9 @@ + + $item + offsetSet @@ -237,7 +240,6 @@ $href $index $item - $item autoEscape ? $this->escapeAttribute($attributes[$itemKey]) : $attributes[$itemKey]]]> autoEscape ? $this->escapeAttribute($value) : $value]]> $value @@ -258,7 +260,6 @@ $href $index $item - $item $media $title $type @@ -277,6 +278,7 @@ + $item $value $value @@ -349,10 +351,6 @@ $content $index $index - attributes['conditional']]]> - type]]> - type]]> - $key autoEscape ? $this->escapeAttribute($value) : $value]]> $value @@ -364,8 +362,6 @@ $content $index - $key - $type $value @@ -374,16 +370,6 @@ isHtml5 - - attributes['conditional']]]> - source]]> - $type - - - attributes]]> - source]]> - type]]> - parent::__call($method, $args) parent::__call($method, $args) @@ -411,6 +397,9 @@ offsetSet + + ObjectShape + $content $index diff --git a/src/Helper/HeadLink.php b/src/Helper/HeadLink.php index 3baea603e..8f34f209a 100644 --- a/src/Helper/HeadLink.php +++ b/src/Helper/HeadLink.php @@ -7,6 +7,7 @@ use Laminas\View\Exception; use Laminas\View\Helper\Placeholder\Container\AbstractContainer; use Laminas\View\Helper\Placeholder\Container\AbstractStandalone; +use stdClass; use function array_intersect; use function array_keys; @@ -36,6 +37,7 @@ * @method HeadLink offsetSetAlternate($index, $href, $type, $title, $extras = []) * @method HeadLink prependAlternate($href, $type, $title, $extras = []) * @method HeadLink setAlternate($href, $type, $title, $extras = []) + * @final */ class HeadLink extends AbstractStandalone { @@ -300,7 +302,7 @@ public function set($value) * * @return string */ - public function itemToString(object $item) + public function itemToString(stdClass $item) { $attributes = (array) $item; $link = ' + * @final */ class HeadMeta extends AbstractStandalone { @@ -254,7 +256,7 @@ public function createData($type, $typeValue, $content, array $modifiers) * @throws Exception\InvalidArgumentException * @return string */ - public function itemToString(object $item) + public function itemToString(stdClass $item) { if (! in_array($item->type, $this->typeKeys)) { throw new Exception\InvalidArgumentException(sprintf( diff --git a/src/Helper/HeadScript.php b/src/Helper/HeadScript.php index 085f01481..78f5c5e19 100644 --- a/src/Helper/HeadScript.php +++ b/src/Helper/HeadScript.php @@ -422,10 +422,10 @@ protected function isValid($value) * * @internal This method will become private in version 3.0 * - * @param mixed $item Item to convert - * @param string $indent String to add before the item - * @param string $escapeStart Starting sequence - * @param string $escapeEnd Ending sequence + * @param ObjectShape $item Item to convert + * @param string $indent String to add before the item + * @param string $escapeStart Starting sequence + * @param string $escapeEnd Ending sequence * @return string */ public function itemToString($item, $indent, $escapeStart, $escapeEnd) diff --git a/src/Helper/HeadStyle.php b/src/Helper/HeadStyle.php index 9bc3df0a0..f8ebe24e5 100644 --- a/src/Helper/HeadStyle.php +++ b/src/Helper/HeadStyle.php @@ -44,6 +44,7 @@ * @method HeadStyle prependStyle(string $content, array $attributes = []) * @method HeadStyle setStyle(string $content, array $attributes = []) * @method HeadStyle setIndent(int|string $indent) + * @final */ class HeadStyle extends AbstractStandalone { @@ -369,7 +370,7 @@ private function styleTagAttributesString(object $item): string * @param string $indent Indentation to use * @return string */ - public function itemToString(object $item, $indent) + public function itemToString(stdClass $item, $indent) { if (! isset($item->content) || ! is_string($item->content) || $item->content === '') { return ''; diff --git a/src/Helper/HeadTitle.php b/src/Helper/HeadTitle.php index 2547ddc12..7718760bb 100644 --- a/src/Helper/HeadTitle.php +++ b/src/Helper/HeadTitle.php @@ -21,6 +21,7 @@ * @method HeadTitle set(string $string) * @method HeadTitle prepend(string $string) * @method HeadTitle append(string $string) + * @final */ class HeadTitle extends AbstractStandalone { diff --git a/src/Helper/InlineScript.php b/src/Helper/InlineScript.php index b8299e50c..465bc4e3b 100644 --- a/src/Helper/InlineScript.php +++ b/src/Helper/InlineScript.php @@ -7,6 +7,8 @@ /** * Helper for setting and retrieving script elements for inclusion in HTML body * section + * + * @final */ class InlineScript extends HeadScript {