4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\DomCrawler;
15 * Any HTML element that can link to an URI.
17 * @author Fabien Potencier <fabien@symfony.com>
19 abstract class AbstractUriElement
27 * @var string|null The method to use for the element
32 * @var string The URI of the page where the element is embedded (or the base href)
34 protected $currentUri;
37 * @param \DOMElement $node A \DOMElement instance
38 * @param string $currentUri The URI of the page where the link is embedded (or the base href)
39 * @param string|null $method The method to use for the link (GET by default)
41 * @throws \InvalidArgumentException if the node is not a link
43 public function __construct(\DOMElement $node, string $currentUri = null, ?string $method = 'GET')
45 $this->setNode($node);
46 $this->method = $method ? strtoupper($method) : null;
47 $this->currentUri = $currentUri;
49 $elementUriIsRelative = null === parse_url(trim($this->getRawUri()), \PHP_URL_SCHEME);
50 $baseUriIsAbsolute = \in_array(strtolower(substr($this->currentUri, 0, 4)), ['http', 'file']);
51 if ($elementUriIsRelative && !$baseUriIsAbsolute) {
52 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));
57 * Gets the node associated with this link.
59 * @return \DOMElement A \DOMElement instance
61 public function getNode()
67 * Gets the method associated with this link.
69 * @return string The method
71 public function getMethod()
73 return $this->method ?? 'GET';
77 * Gets the URI associated with this link.
79 * @return string The URI
81 public function getUri()
83 $uri = trim($this->getRawUri());
86 if (null !== parse_url($uri, \PHP_URL_SCHEME)) {
92 return $this->currentUri;
96 if ('#' === $uri[0]) {
97 return $this->cleanupAnchor($this->currentUri).$uri;
100 $baseUri = $this->cleanupUri($this->currentUri);
102 if ('?' === $uri[0]) {
103 return $baseUri.$uri;
106 // absolute URL with relative schema
107 if (0 === strpos($uri, '//')) {
108 return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri;
111 $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri);
114 if ('/' === $uri[0]) {
115 return $baseUri.$uri;
119 $path = parse_url(substr($this->currentUri, \strlen($baseUri)), \PHP_URL_PATH);
120 $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri);
122 return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path;
126 * Returns raw URI data.
130 abstract protected function getRawUri();
133 * Returns the canonicalized URI path (see RFC 3986, section 5.2.4).
135 * @param string $path URI path
139 protected function canonicalizePath($path)
141 if ('' === $path || '/' === $path) {
145 if ('.' === substr($path, -1)) {
151 foreach (explode('/', $path) as $segment) {
152 if ('..' === $segment) {
154 } elseif ('.' !== $segment) {
155 $output[] = $segment;
159 return implode('/', $output);
163 * Sets current \DOMElement instance.
165 * @param \DOMElement $node A \DOMElement instance
167 * @throws \LogicException If given node is not an anchor
169 abstract protected function setNode(\DOMElement $node);
172 * Removes the query string and the anchor from the given uri.
174 private function cleanupUri(string $uri): string
176 return $this->cleanupQuery($this->cleanupAnchor($uri));
180 * Remove the query string from the uri.
182 private function cleanupQuery(string $uri): string
184 if (false !== $pos = strpos($uri, '?')) {
185 return substr($uri, 0, $pos);
192 * Remove the anchor from the uri.
194 private function cleanupAnchor(string $uri): string
196 if (false !== $pos = strpos($uri, '#')) {
197 return substr($uri, 0, $pos);