diff --git a/AbstractUriElement.php b/AbstractUriElement.php index 55c71d6..5ec7b3e 100644 --- a/AbstractUriElement.php +++ b/AbstractUriElement.php @@ -20,7 +20,6 @@ abstract class AbstractUriElement { protected \DOMElement $node; protected ?string $method; - protected ?string $currentUri; /** * @param \DOMElement $node A \DOMElement instance @@ -29,16 +28,18 @@ abstract class AbstractUriElement * * @throws \InvalidArgumentException if the node is not a link */ - public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = 'GET') - { + public function __construct( + \DOMElement $node, + protected ?string $currentUri = null, + ?string $method = 'GET', + ) { $this->setNode($node); $this->method = $method ? strtoupper($method) : null; - $this->currentUri = $currentUri; $elementUriIsRelative = !parse_url(trim($this->getRawUri()), \PHP_URL_SCHEME); $baseUriIsAbsolute = null !== $this->currentUri && \in_array(strtolower(substr($this->currentUri, 0, 4)), ['http', 'file']); if ($elementUriIsRelative && !$baseUriIsAbsolute) { - throw new \InvalidArgumentException(sprintf('The URL of the element is relative, so you must define its base URI passing an absolute URL to the constructor of the "%s" class ("%s" was passed).', __CLASS__, $this->currentUri)); + throw new \InvalidArgumentException(\sprintf('The URL of the element is relative, so you must define its base URI passing an absolute URL to the constructor of the "%s" class ("%s" was passed).', __CLASS__, $this->currentUri)); } } diff --git a/Crawler.php b/Crawler.php index a328dd0..e41875e 100644 --- a/Crawler.php +++ b/Crawler.php @@ -23,8 +23,6 @@ */ class Crawler implements \Countable, \IteratorAggregate { - protected ?string $uri; - /** * The default namespace prefix to be used with XPath and CSS expressions. */ @@ -60,9 +58,12 @@ class Crawler implements \Countable, \IteratorAggregate /** * @param \DOMNodeList|\DOMNode|\DOMNode[]|string|null $node A Node to use as the base for the crawling */ - public function __construct(\DOMNodeList|\DOMNode|array|string|null $node = null, ?string $uri = null, ?string $baseHref = null, bool $useHtml5Parser = true) - { - $this->uri = $uri; + public function __construct( + \DOMNodeList|\DOMNode|array|string|null $node = null, + protected ?string $uri = null, + ?string $baseHref = null, + bool $useHtml5Parser = true, + ) { $this->baseHref = $baseHref ?: $uri; $this->html5Parser = $useHtml5Parser ? new HTML5(['disable_html_ns' => true]) : null; $this->cachedNamespaces = new \ArrayObject(); @@ -117,7 +118,7 @@ public function add(\DOMNodeList|\DOMNode|array|string|null $node): void } elseif (\is_string($node)) { $this->addContent($node); } elseif (null !== $node) { - throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', get_debug_type($node))); } } @@ -410,7 +411,7 @@ public function closest(string $selector): ?self $domNode = $this->getNode(0); - while (\XML_ELEMENT_NODE === $domNode->nodeType) { + while (null !== $domNode && \XML_ELEMENT_NODE === $domNode->nodeType) { $node = $this->createSubCrawler($domNode); if ($node->matches($selector)) { return $node; @@ -731,7 +732,7 @@ public function filter(string $selector): static public function selectLink(string $value): static { return $this->filterRelativeXPath( - sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %1$s) or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %1$s)]]', static::xpathLiteral(' '.$value.' ')) + \sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %1$s) or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %1$s)]]', static::xpathLiteral(' '.$value.' ')) ); } @@ -740,7 +741,7 @@ public function selectLink(string $value): static */ public function selectImage(string $value): static { - $xpath = sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', static::xpathLiteral($value)); + $xpath = \sprintf('descendant-or-self::img[contains(normalize-space(string(@alt)), %s)]', static::xpathLiteral($value)); return $this->filterRelativeXPath($xpath); } @@ -751,7 +752,7 @@ public function selectImage(string $value): static public function selectButton(string $value): static { return $this->filterRelativeXPath( - sprintf('descendant-or-self::input[((contains(%1$s, "submit") or contains(%1$s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %2$s)) or (contains(%1$s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %2$s)) or @id=%3$s or @name=%3$s] | descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %2$s) or @id=%3$s or @name=%3$s]', 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value)) + \sprintf('descendant-or-self::input[((contains(%1$s, "submit") or contains(%1$s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %2$s)) or (contains(%1$s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %2$s)) or @id=%3$s or @name=%3$s] | descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %2$s) or @id=%3$s or @name=%3$s]', 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value)) ); } @@ -769,7 +770,7 @@ public function link(string $method = 'get'): Link $node = $this->getNode(0); if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); } return new Link($node, $this->baseHref, $method); @@ -787,7 +788,7 @@ public function links(): array $links = []; foreach ($this->nodes as $node) { if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_debug_type($node))); } $links[] = new Link($node, $this->baseHref, 'get'); @@ -810,7 +811,7 @@ public function image(): Image $node = $this->getNode(0); if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); } return new Image($node, $this->baseHref); @@ -826,7 +827,7 @@ public function images(): array $images = []; foreach ($this as $node) { if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_debug_type($node))); } $images[] = new Image($node, $this->baseHref); @@ -849,7 +850,7 @@ public function form(?array $values = null, ?string $method = null): Form $node = $this->getNode(0); if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); + throw new \InvalidArgumentException(\sprintf('The selected node should be instance of DOMElement, got "%s".', get_debug_type($node))); } $form = new Form($node, $this->uri, $method, $this->baseHref); @@ -893,18 +894,18 @@ public function registerNamespace(string $prefix, string $namespace): void public static function xpathLiteral(string $s): string { if (!str_contains($s, "'")) { - return sprintf("'%s'", $s); + return \sprintf("'%s'", $s); } if (!str_contains($s, '"')) { - return sprintf('"%s"', $s); + return \sprintf('"%s"', $s); } $string = $s; $parts = []; while (true) { if (false !== $pos = strpos($string, "'")) { - $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = \sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { @@ -913,7 +914,7 @@ public static function xpathLiteral(string $s): string } } - return sprintf('concat(%s)', implode(', ', $parts)); + return \sprintf('concat(%s)', implode(', ', $parts)); } /** @@ -1153,7 +1154,7 @@ private function discoverNamespace(\DOMXPath $domxpath, string $prefix): ?string } // ask for one namespace, otherwise we'd get a collection with an item for each node - $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); + $namespaces = $domxpath->query(\sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); return $this->cachedNamespaces[$prefix] = ($node = $namespaces->item(0)) ? $node->nodeValue : null; } diff --git a/Field/ChoiceFormField.php b/Field/ChoiceFormField.php index 57329b1..25fba30 100644 --- a/Field/ChoiceFormField.php +++ b/Field/ChoiceFormField.php @@ -78,7 +78,7 @@ public function select(string|array|bool $value): void public function tick(): void { if ('checkbox' !== $this->type) { - throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + throw new \LogicException(\sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(true); @@ -92,7 +92,7 @@ public function tick(): void public function untick(): void { if ('checkbox' !== $this->type) { - throw new \LogicException(sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + throw new \LogicException(\sprintf('You cannot untick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); } $this->setValue(false); @@ -114,16 +114,16 @@ public function setValue(string|array|bool|null $value): void } else { if (\is_array($value)) { if (!$this->multiple) { - throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); + throw new \InvalidArgumentException(\sprintf('The value for "%s" cannot be an array.', $this->name)); } foreach ($value as $v) { if (!$this->containsOption($v, $this->options)) { - throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $v, implode('", "', $this->availableOptionValues()))); + throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $v, implode('", "', $this->availableOptionValues()))); } } } elseif (!$this->containsOption($value, $this->options)) { - throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $value, implode('", "', $this->availableOptionValues()))); + throw new \InvalidArgumentException(\sprintf('Input "%s" cannot take "%s" as a value (possible values: "%s").', $this->name, $value, implode('", "', $this->availableOptionValues()))); } if ($this->multiple) { @@ -148,7 +148,7 @@ public function setValue(string|array|bool|null $value): void public function addChoice(\DOMElement $node): void { if (!$this->multiple && 'radio' !== $this->type) { - throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); + throw new \LogicException(\sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); } $option = $this->buildOptionValue($node); @@ -183,11 +183,11 @@ public function isMultiple(): bool protected function initialize(): void { if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); + throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); } if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { - throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is "%s").', $this->node->getAttribute('type'))); + throw new \LogicException(\sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is "%s").', $this->node->getAttribute('type'))); } $this->value = null; diff --git a/Field/FileFormField.php b/Field/FileFormField.php index e125a3d..5580fd8 100644 --- a/Field/FileFormField.php +++ b/Field/FileFormField.php @@ -29,7 +29,7 @@ public function setErrorCode(int $error): void { $codes = [\UPLOAD_ERR_INI_SIZE, \UPLOAD_ERR_FORM_SIZE, \UPLOAD_ERR_PARTIAL, \UPLOAD_ERR_NO_FILE, \UPLOAD_ERR_NO_TMP_DIR, \UPLOAD_ERR_CANT_WRITE, \UPLOAD_ERR_EXTENSION]; if (!\in_array($error, $codes)) { - throw new \InvalidArgumentException(sprintf('The error code "%s" is not valid.', $error)); + throw new \InvalidArgumentException(\sprintf('The error code "%s" is not valid.', $error)); } $this->value = ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0]; @@ -55,8 +55,9 @@ public function setValue(?string $value): void $name = $info['basename']; // copy to a tmp location - $tmp = sys_get_temp_dir().'/'.strtr(substr(base64_encode(hash('xxh128', uniqid(mt_rand(), true), true)), 0, 7), '/', '_'); + $tmp = tempnam(sys_get_temp_dir(), $name); if (\array_key_exists('extension', $info)) { + unlink($tmp); $tmp .= '.'.$info['extension']; } if (is_file($tmp)) { @@ -90,11 +91,11 @@ public function setFilePath(string $path): void protected function initialize(): void { if ('input' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); + throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); } if ('file' !== strtolower($this->node->getAttribute('type'))) { - throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is "%s").', $this->node->getAttribute('type'))); + throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag with a type of file (given type is "%s").', $this->node->getAttribute('type'))); } $this->setValue(null); diff --git a/Field/FormField.php b/Field/FormField.php index f364d52..fb1a0ae 100644 --- a/Field/FormField.php +++ b/Field/FormField.php @@ -18,7 +18,6 @@ */ abstract class FormField { - protected \DOMElement $node; protected string $name; protected string|array|null $value = null; protected \DOMDocument $document; @@ -28,9 +27,9 @@ abstract class FormField /** * @param \DOMElement $node The node associated with this field */ - public function __construct(\DOMElement $node) - { - $this->node = $node; + public function __construct( + protected \DOMElement $node, + ) { $this->name = $node->getAttribute('name'); $this->xpath = new \DOMXPath($node->ownerDocument); @@ -45,7 +44,7 @@ public function getLabel(): ?\DOMElement $xpath = new \DOMXPath($this->node->ownerDocument); if ($this->node->hasAttribute('id')) { - $labels = $xpath->query(sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id'))); + $labels = $xpath->query(\sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id'))); if ($labels->length > 0) { return $labels->item(0); } diff --git a/Field/InputFormField.php b/Field/InputFormField.php index 2fd43d4..1e26e5c 100644 --- a/Field/InputFormField.php +++ b/Field/InputFormField.php @@ -29,7 +29,7 @@ class InputFormField extends FormField protected function initialize(): void { if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { - throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); + throw new \LogicException(\sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); } $type = strtolower($this->node->getAttribute('type')); diff --git a/Field/TextareaFormField.php b/Field/TextareaFormField.php index 46b151f..b246776 100644 --- a/Field/TextareaFormField.php +++ b/Field/TextareaFormField.php @@ -26,7 +26,7 @@ class TextareaFormField extends FormField protected function initialize(): void { if ('textarea' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); + throw new \LogicException(\sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); } $this->value = ''; diff --git a/Form.php b/Form.php index a634d05..54ed5d9 100644 --- a/Form.php +++ b/Form.php @@ -23,7 +23,6 @@ class Form extends Link implements \ArrayAccess { private \DOMElement $button; private FormFieldRegistry $fields; - private ?string $baseHref; /** * @param \DOMElement $node A \DOMElement instance @@ -33,10 +32,13 @@ class Form extends Link implements \ArrayAccess * * @throws \LogicException if the node is not a button inside a form tag */ - public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = null, ?string $baseHref = null) - { + public function __construct( + \DOMElement $node, + ?string $currentUri = null, + ?string $method = null, + private ?string $baseHref = null, + ) { parent::__construct($node, $currentUri, $method); - $this->baseHref = $baseHref; $this->initialize(); } @@ -359,7 +361,7 @@ protected function setNode(\DOMElement $node): void $formId = $node->getAttribute('form'); $form = $node->ownerDocument->getElementById($formId); if (null === $form) { - throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); + throw new \LogicException(\sprintf('The selected node has an invalid form attribute (%s).', $formId)); } $this->node = $form; @@ -372,7 +374,7 @@ protected function setNode(\DOMElement $node): void } } while ('form' !== $node->nodeName); } elseif ('form' !== $node->nodeName) { - throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); + throw new \LogicException(\sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); } $this->node = $node; @@ -417,17 +419,15 @@ private function initialize(): void // corresponding elements are either descendants or have a matching HTML5 form attribute $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); - $fieldNodes = $xpath->query(sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[( not(ancestor::template) or ancestor::turbo-stream )]', $formId)); - foreach ($fieldNodes as $node) { - $this->addField($node); - } + $fieldNodes = $xpath->query(\sprintf('( descendant::input[@form=%s] | descendant::button[@form=%1$s] | descendant::textarea[@form=%1$s] | descendant::select[@form=%1$s] | //form[@id=%1$s]//input[not(@form)] | //form[@id=%1$s]//button[not(@form)] | //form[@id=%1$s]//textarea[not(@form)] | //form[@id=%1$s]//select[not(@form)] )[( not(ancestor::template) or ancestor::turbo-stream )]', $formId)); } else { // do the xpath query with $this->node as the context node, to only find descendant elements // however, descendant elements with form attribute are not part of this form $fieldNodes = $xpath->query('( descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)] )[( not(ancestor::template) or ancestor::turbo-stream )]', $this->node); - foreach ($fieldNodes as $node) { - $this->addField($node); - } + } + + foreach ($fieldNodes as $node) { + $this->addField($node); } if ($this->baseHref && '' !== $this->node->getAttribute('action')) { diff --git a/FormFieldRegistry.php b/FormFieldRegistry.php index 2a4e586..ef8b644 100644 --- a/FormFieldRegistry.php +++ b/FormFieldRegistry.php @@ -76,7 +76,7 @@ public function &get(string $name): FormField|array while ($segments) { $path = array_shift($segments); if (!\is_array($target) || !\array_key_exists($path, $target)) { - throw new \InvalidArgumentException(sprintf('Unreachable field "%s".', $path)); + throw new \InvalidArgumentException(\sprintf('Unreachable field "%s".', $path)); } $target = &$target[$path]; } @@ -116,7 +116,7 @@ public function set(string $name, mixed $value): void $this->set($k, $v); } } else { - throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); + throw new \InvalidArgumentException(\sprintf('Cannot set value on a compound field "%s".', $name)); } } @@ -136,7 +136,7 @@ public function all(): array private function walk(array $array, ?string $base = '', array &$output = []): array { foreach ($array as $k => $v) { - $path = $base ? sprintf('%s[%s]', $base, $k) : $k; + $path = $base ? \sprintf('%s[%s]', $base, $k) : $k; if (\is_array($v)) { $this->walk($v, $path, $output); } else { diff --git a/Image.php b/Image.php index dc7c0b4..964b978 100644 --- a/Image.php +++ b/Image.php @@ -29,7 +29,7 @@ protected function getRawUri(): string protected function setNode(\DOMElement $node): void { if ('img' !== $node->nodeName) { - throw new \LogicException(sprintf('Unable to visualize a "%s" tag.', $node->nodeName)); + throw new \LogicException(\sprintf('Unable to visualize a "%s" tag.', $node->nodeName)); } $this->node = $node; diff --git a/Link.php b/Link.php index d6c273a..da08de4 100644 --- a/Link.php +++ b/Link.php @@ -26,7 +26,7 @@ protected function getRawUri(): string protected function setNode(\DOMElement $node): void { if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { - throw new \LogicException(sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); + throw new \LogicException(\sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); } $this->node = $node; diff --git a/Test/Constraint/CrawlerAnySelectorTextContains.php b/Test/Constraint/CrawlerAnySelectorTextContains.php index f209499..33fe1a4 100644 --- a/Test/Constraint/CrawlerAnySelectorTextContains.php +++ b/Test/Constraint/CrawlerAnySelectorTextContains.php @@ -16,29 +16,27 @@ final class CrawlerAnySelectorTextContains extends Constraint { - private string $selector; - private string $expectedText; private bool $hasNode = false; - public function __construct(string $selector, string $expectedText) - { - $this->selector = $selector; - $this->expectedText = $expectedText; + public function __construct( + private string $selector, + private string $expectedText, + ) { } public function toString(): string { if ($this->hasNode) { - return sprintf('the text of any node matching selector "%s" contains "%s"', $this->selector, $this->expectedText); + return \sprintf('the text of any node matching selector "%s" contains "%s"', $this->selector, $this->expectedText); } - return sprintf('the Crawler has a node matching selector "%s"', $this->selector); + return \sprintf('the Crawler has a node matching selector "%s"', $this->selector); } protected function matches($other): bool { if (!$other instanceof Crawler) { - throw new \InvalidArgumentException(sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); + throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); } $other = $other->filter($this->selector); @@ -61,7 +59,7 @@ protected function matches($other): bool protected function failureDescription($other): string { if (!$other instanceof Crawler) { - throw new \InvalidArgumentException(sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); + throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); } return $this->toString(); diff --git a/Test/Constraint/CrawlerAnySelectorTextSame.php b/Test/Constraint/CrawlerAnySelectorTextSame.php index f4c8320..8e4bee0 100644 --- a/Test/Constraint/CrawlerAnySelectorTextSame.php +++ b/Test/Constraint/CrawlerAnySelectorTextSame.php @@ -16,24 +16,21 @@ final class CrawlerAnySelectorTextSame extends Constraint { - private string $selector; - private string $expectedText; - - public function __construct(string $selector, string $expectedText) - { - $this->selector = $selector; - $this->expectedText = $expectedText; + public function __construct( + private string $selector, + private string $expectedText, + ) { } public function toString(): string { - return sprintf('has at least a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); + return \sprintf('has at least a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); } protected function matches($other): bool { if (!$other instanceof Crawler) { - throw new \InvalidArgumentException(sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); + throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); } $other = $other->filter($this->selector); @@ -49,7 +46,7 @@ protected function matches($other): bool protected function failureDescription($other): string { if (!$other instanceof Crawler) { - throw new \InvalidArgumentException(sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); + throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); } return 'the Crawler '.$this->toString(); diff --git a/Test/Constraint/CrawlerSelectorAttributeValueSame.php b/Test/Constraint/CrawlerSelectorAttributeValueSame.php index f8df35b..2657792 100644 --- a/Test/Constraint/CrawlerSelectorAttributeValueSame.php +++ b/Test/Constraint/CrawlerSelectorAttributeValueSame.php @@ -16,20 +16,16 @@ final class CrawlerSelectorAttributeValueSame extends Constraint { - private string $selector; - private string $attribute; - private string $expectedText; - - public function __construct(string $selector, string $attribute, string $expectedText) - { - $this->selector = $selector; - $this->attribute = $attribute; - $this->expectedText = $expectedText; + public function __construct( + private string $selector, + private string $attribute, + private string $expectedText, + ) { } public function toString(): string { - return sprintf('has a node matching selector "%s" with attribute "%s" of value "%s"', $this->selector, $this->attribute, $this->expectedText); + return \sprintf('has a node matching selector "%s" with attribute "%s" of value "%s"', $this->selector, $this->attribute, $this->expectedText); } /** diff --git a/Test/Constraint/CrawlerSelectorCount.php b/Test/Constraint/CrawlerSelectorCount.php index 22ee1db..f56ad3b 100644 --- a/Test/Constraint/CrawlerSelectorCount.php +++ b/Test/Constraint/CrawlerSelectorCount.php @@ -24,7 +24,7 @@ public function __construct( public function toString(): string { - return sprintf('selector "%s" count is "%d"', $this->selector, $this->count); + return \sprintf('selector "%s" count is "%d"', $this->selector, $this->count); } /** @@ -40,6 +40,6 @@ protected function matches($crawler): bool */ protected function failureDescription($crawler): string { - return sprintf('the Crawler selector "%s" was expected to be found %d time(s) but was found %d time(s)', $this->selector, $this->count, \count($crawler->filter($this->selector))); + return \sprintf('the Crawler selector "%s" was expected to be found %d time(s) but was found %d time(s)', $this->selector, $this->count, \count($crawler->filter($this->selector))); } } diff --git a/Test/Constraint/CrawlerSelectorExists.php b/Test/Constraint/CrawlerSelectorExists.php index 5d0c100..7d9a607 100644 --- a/Test/Constraint/CrawlerSelectorExists.php +++ b/Test/Constraint/CrawlerSelectorExists.php @@ -16,16 +16,14 @@ final class CrawlerSelectorExists extends Constraint { - private string $selector; - - public function __construct(string $selector) - { - $this->selector = $selector; + public function __construct( + private string $selector, + ) { } public function toString(): string { - return sprintf('matches selector "%s"', $this->selector); + return \sprintf('matches selector "%s"', $this->selector); } /** diff --git a/Test/Constraint/CrawlerSelectorTextContains.php b/Test/Constraint/CrawlerSelectorTextContains.php index c70fff1..add2b87 100644 --- a/Test/Constraint/CrawlerSelectorTextContains.php +++ b/Test/Constraint/CrawlerSelectorTextContains.php @@ -16,24 +16,22 @@ final class CrawlerSelectorTextContains extends Constraint { - private string $selector; - private string $expectedText; private bool $hasNode = false; private string $nodeText; - public function __construct(string $selector, string $expectedText) - { - $this->selector = $selector; - $this->expectedText = $expectedText; + public function __construct( + private string $selector, + private string $expectedText, + ) { } public function toString(): string { if ($this->hasNode) { - return sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText); + return \sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText); } - return sprintf('the Crawler has a node matching selector "%s"', $this->selector); + return \sprintf('the Crawler has a node matching selector "%s"', $this->selector); } /** diff --git a/Test/Constraint/CrawlerSelectorTextSame.php b/Test/Constraint/CrawlerSelectorTextSame.php index 269e23f..580e9c3 100644 --- a/Test/Constraint/CrawlerSelectorTextSame.php +++ b/Test/Constraint/CrawlerSelectorTextSame.php @@ -16,18 +16,15 @@ final class CrawlerSelectorTextSame extends Constraint { - private string $selector; - private string $expectedText; - - public function __construct(string $selector, string $expectedText) - { - $this->selector = $selector; - $this->expectedText = $expectedText; + public function __construct( + private string $selector, + private string $expectedText, + ) { } public function toString(): string { - return sprintf('has a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); + return \sprintf('has a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); } /** diff --git a/Tests/AbstractCrawlerTestCase.php b/Tests/AbstractCrawlerTestCase.php index 97b16b9..5cdbbbf 100644 --- a/Tests/AbstractCrawlerTestCase.php +++ b/Tests/AbstractCrawlerTestCase.php @@ -1031,6 +1031,29 @@ public function testClosest() $this->assertNull($notFound); } + public function testClosestWithOrphanedNode() + { + $html = <<<'HTML' + + +
+
+
+ + +HTML; + + $crawler = $this->createCrawler($this->getDoctype().$html); + $foo = $crawler->filter('#foo'); + + $fooNode = $foo->getNode(0); + + $fooNode->parentNode->replaceChild($fooNode->ownerDocument->createElement('ol'), $fooNode); + + $body = $foo->closest('body'); + $this->assertNull($body); + } + public function testOuterHtml() { $html = <<<'HTML' diff --git a/Tests/Field/FileFormFieldTest.php b/Tests/Field/FileFormFieldTest.php index 7934402..00e84b1 100644 --- a/Tests/Field/FileFormFieldTest.php +++ b/Tests/Field/FileFormFieldTest.php @@ -71,9 +71,16 @@ public function testSetValue($method) $field->$method(__DIR__.'/../Fixtures/no-extension'); $value = $field->getValue(); + $tmpName = $value['tmp_name']; + + // Windows creates temporary files with a .tmp extension + if ('\\' === \DIRECTORY_SEPARATOR && str_ends_with($tmpName, '.tmp')) { + $tmpName = substr($tmpName, 0, -4); + } + $this->assertArrayNotHasKey( 'extension', - pathinfo($value['tmp_name']), + pathinfo($tmpName), "->$method() does not add a file extension in the tmp_name copy" ); } diff --git a/Tests/FormTest.php b/Tests/FormTest.php index fcbd216..a169831 100644 --- a/Tests/FormTest.php +++ b/Tests/FormTest.php @@ -252,7 +252,7 @@ public static function provideInitializeValues() 'appends the submitted button value but not other submit buttons', ' ', - ['foobar' => ['InputFormField', 'foobar']], + ['foobar' => ['InputFormField', 'foobar']], ], [ 'turns an image input into x and y fields', @@ -263,38 +263,38 @@ public static function provideInitializeValues() 'returns textareas', ' ', - ['foo' => ['TextareaFormField', 'foo']], + ['foo' => ['TextareaFormField', 'foo']], ], [ 'returns inputs', ' ', - ['foo' => ['InputFormField', 'foo']], + ['foo' => ['InputFormField', 'foo']], ], [ 'returns checkboxes', ' ', - ['foo' => ['ChoiceFormField', 'foo']], + ['foo' => ['ChoiceFormField', 'foo']], ], [ 'returns not-checked checkboxes', ' ', - ['foo' => ['ChoiceFormField', false]], + ['foo' => ['ChoiceFormField', false]], ], [ 'returns radio buttons', ' ', - ['foo' => ['ChoiceFormField', 'bar']], + ['foo' => ['ChoiceFormField', 'bar']], ], [ 'returns file inputs', ' ', - ['foo' => ['FileFormField', ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0]]], + ['foo' => ['FileFormField', ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0]]], ], ]; } @@ -830,7 +830,7 @@ public function testFormRegistrySetValues() 3 => 3, 'bar' => [ 'baz' => 'fbb', - ], + ], ]); } diff --git a/Tests/Html5ParserCrawlerTest.php b/Tests/Html5ParserCrawlerTest.php index 58c1db8..79b8b51 100644 --- a/Tests/Html5ParserCrawlerTest.php +++ b/Tests/Html5ParserCrawlerTest.php @@ -43,7 +43,7 @@ public function testHtml5ParserWithInvalidHeadedContent(string $content) { $crawler = $this->createCrawler(); $crawler->addHtmlContent($content); - self::assertEmpty($crawler->filterXPath('//h1')->text(), '->addHtmlContent failed as expected'); + self::assertSame('', $crawler->filterXPath('//h1')->text(), '->addHtmlContent failed as expected'); } public function testHtml5ParserNotSameAsNativeParserForSpecificHtml() diff --git a/Tests/ImageTest.php b/Tests/ImageTest.php index 4fdf7a9..61c448a 100644 --- a/Tests/ImageTest.php +++ b/Tests/ImageTest.php @@ -50,7 +50,7 @@ public function testAbsoluteBaseUriIsMandatoryWhenImageUrlIsRelative() public function testGetUri($url, $currentUri, $expected) { $dom = new \DOMDocument(); - $dom->loadHTML(sprintf('foo', $url)); + $dom->loadHTML(\sprintf('foo', $url)); $image = new Image($dom->getElementsByTagName('img')->item(0), $currentUri); $this->assertEquals($expected, $image->getUri()); diff --git a/Tests/LinkTest.php b/Tests/LinkTest.php index 9349088..3360c32 100644 --- a/Tests/LinkTest.php +++ b/Tests/LinkTest.php @@ -75,7 +75,7 @@ public function testGetMethod() public function testGetUri($url, $currentUri, $expected) { $dom = new \DOMDocument(); - $dom->loadHTML(sprintf('foo', $url)); + $dom->loadHTML(\sprintf('foo', $url)); $link = new Link($dom->getElementsByTagName('a')->item(0), $currentUri); $this->assertEquals($expected, $link->getUri()); @@ -87,7 +87,7 @@ public function testGetUri($url, $currentUri, $expected) public function testGetUriOnArea($url, $currentUri, $expected) { $dom = new \DOMDocument(); - $dom->loadHTML(sprintf('', $url)); + $dom->loadHTML(\sprintf('', $url)); $link = new Link($dom->getElementsByTagName('area')->item(0), $currentUri); $this->assertEquals($expected, $link->getUri()); @@ -99,7 +99,7 @@ public function testGetUriOnArea($url, $currentUri, $expected) public function testGetUriOnLink($url, $currentUri, $expected) { $dom = new \DOMDocument(); - $dom->loadHTML(sprintf('', $url)); + $dom->loadHTML(\sprintf('', $url)); $link = new Link($dom->getElementsByTagName('link')->item(0), $currentUri); $this->assertEquals($expected, $link->getUri()); diff --git a/Tests/Test/Constraint/CrawlerAnySelectorTextContainsTest.php b/Tests/Test/Constraint/CrawlerAnySelectorTextContainsTest.php index d3c4d8a..d76335a 100644 --- a/Tests/Test/Constraint/CrawlerAnySelectorTextContainsTest.php +++ b/Tests/Test/Constraint/CrawlerAnySelectorTextContainsTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Test\Constraint\CrawlerAnySelectorTextContains; @@ -27,23 +26,25 @@ public function testConstraint() self::assertTrue($constraint->evaluate(new Crawler('