annotate vendor/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@18 1 <?php
Chris@18 2 namespace TYPO3\PharStreamWrapper\Resolver;
Chris@18 3
Chris@18 4 /*
Chris@18 5 * This file is part of the TYPO3 project.
Chris@18 6 *
Chris@18 7 * It is free software; you can redistribute it and/or modify it under the terms
Chris@18 8 * of the MIT License (MIT). For the full copyright and license information,
Chris@18 9 * please read the LICENSE file that was distributed with this source code.
Chris@18 10 *
Chris@18 11 * The TYPO3 project - inspiring people to share!
Chris@18 12 */
Chris@18 13
Chris@18 14 use TYPO3\PharStreamWrapper\Helper;
Chris@18 15 use TYPO3\PharStreamWrapper\Manager;
Chris@18 16 use TYPO3\PharStreamWrapper\Phar\Reader;
Chris@18 17 use TYPO3\PharStreamWrapper\Resolvable;
Chris@18 18
Chris@18 19 class PharInvocationResolver implements Resolvable
Chris@18 20 {
Chris@18 21 const RESOLVE_REALPATH = 1;
Chris@18 22 const RESOLVE_ALIAS = 2;
Chris@18 23 const ASSERT_INTERNAL_INVOCATION = 32;
Chris@18 24
Chris@18 25 /**
Chris@18 26 * @var string[]
Chris@18 27 */
Chris@18 28 private $invocationFunctionNames = array(
Chris@18 29 'include',
Chris@18 30 'include_once',
Chris@18 31 'require',
Chris@18 32 'require_once'
Chris@18 33 );
Chris@18 34
Chris@18 35 /**
Chris@18 36 * Contains resolved base names in order to reduce file IO.
Chris@18 37 *
Chris@18 38 * @var string[]
Chris@18 39 */
Chris@18 40 private $baseNames = array();
Chris@18 41
Chris@18 42 /**
Chris@18 43 * Resolves PharInvocation value object (baseName and optional alias).
Chris@18 44 *
Chris@18 45 * Phar aliases are intended to be used only inside Phar archives, however
Chris@18 46 * PharStreamWrapper needs this information exposed outside of Phar as well
Chris@18 47 * It is possible that same alias is used for different $baseName values.
Chris@18 48 * That's why PharInvocationCollection behaves like a stack when resolving
Chris@18 49 * base-name for a given alias. On the other hand it is not possible that
Chris@18 50 * one $baseName is referring to multiple aliases.
Chris@18 51 * @see https://secure.php.net/manual/en/phar.setalias.php
Chris@18 52 * @see https://secure.php.net/manual/en/phar.mapphar.php
Chris@18 53 *
Chris@18 54 * @param string $path
Chris@18 55 * @param int|null $flags
Chris@18 56 * @return null|PharInvocation
Chris@18 57 */
Chris@18 58 public function resolve($path, $flags = null)
Chris@18 59 {
Chris@18 60 $hasPharPrefix = Helper::hasPharPrefix($path);
Chris@18 61 if ($flags === null) {
Chris@18 62 $flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS | static::ASSERT_INTERNAL_INVOCATION;
Chris@18 63 }
Chris@18 64
Chris@18 65 if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) {
Chris@18 66 $invocation = $this->findByAlias($path);
Chris@18 67 if ($invocation !== null) {
Chris@18 68 return $invocation;
Chris@18 69 }
Chris@18 70 }
Chris@18 71
Chris@18 72 $baseName = $this->resolveBaseName($path, $flags);
Chris@18 73 if ($baseName === null) {
Chris@18 74 return null;
Chris@18 75 }
Chris@18 76
Chris@18 77 if ($flags & static::RESOLVE_REALPATH) {
Chris@18 78 $baseName = $this->baseNames[$baseName];
Chris@18 79 }
Chris@18 80
Chris@18 81 return $this->retrieveInvocation($baseName, $flags);
Chris@18 82 }
Chris@18 83
Chris@18 84 /**
Chris@18 85 * Retrieves PharInvocation, either existing in collection or created on demand
Chris@18 86 * with resolving a potential alias name used in the according Phar archive.
Chris@18 87 *
Chris@18 88 * @param string $baseName
Chris@18 89 * @param int $flags
Chris@18 90 * @return PharInvocation
Chris@18 91 */
Chris@18 92 private function retrieveInvocation($baseName, $flags)
Chris@18 93 {
Chris@18 94 $invocation = $this->findByBaseName($baseName);
Chris@18 95 if ($invocation !== null) {
Chris@18 96 return $invocation;
Chris@18 97 }
Chris@18 98
Chris@18 99 if ($flags & static::RESOLVE_ALIAS) {
Chris@18 100 $reader = new Reader($baseName);
Chris@18 101 $alias = $reader->resolveContainer()->getAlias();
Chris@18 102 } else {
Chris@18 103 $alias = '';
Chris@18 104 }
Chris@18 105 // add unconfirmed(!) new invocation to collection
Chris@18 106 $invocation = new PharInvocation($baseName, $alias);
Chris@18 107 Manager::instance()->getCollection()->collect($invocation);
Chris@18 108 return $invocation;
Chris@18 109 }
Chris@18 110
Chris@18 111 /**
Chris@18 112 * @param string $path
Chris@18 113 * @param int $flags
Chris@18 114 * @return null|string
Chris@18 115 */
Chris@18 116 private function resolveBaseName($path, $flags)
Chris@18 117 {
Chris@18 118 $baseName = $this->findInBaseNames($path);
Chris@18 119 if ($baseName !== null) {
Chris@18 120 return $baseName;
Chris@18 121 }
Chris@18 122
Chris@18 123 $baseName = Helper::determineBaseFile($path);
Chris@18 124 if ($baseName !== null) {
Chris@18 125 $this->addBaseName($baseName);
Chris@18 126 return $baseName;
Chris@18 127 }
Chris@18 128
Chris@18 129 $possibleAlias = $this->resolvePossibleAlias($path);
Chris@18 130 if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) {
Chris@18 131 return null;
Chris@18 132 }
Chris@18 133
Chris@18 134 $trace = debug_backtrace();
Chris@18 135 foreach ($trace as $item) {
Chris@18 136 if (!isset($item['function']) || !isset($item['args'][0])
Chris@18 137 || !in_array($item['function'], $this->invocationFunctionNames, true)) {
Chris@18 138 continue;
Chris@18 139 }
Chris@18 140 $currentPath = $item['args'][0];
Chris@18 141 if (Helper::hasPharPrefix($currentPath)) {
Chris@18 142 continue;
Chris@18 143 }
Chris@18 144 $currentBaseName = Helper::determineBaseFile($currentPath);
Chris@18 145 if ($currentBaseName === null) {
Chris@18 146 continue;
Chris@18 147 }
Chris@18 148 // ensure the possible alias name (how we have been called initially) matches
Chris@18 149 // the resolved alias name that was retrieved by the current possible base name
Chris@18 150 $reader = new Reader($currentBaseName);
Chris@18 151 $currentAlias = $reader->resolveContainer()->getAlias();
Chris@18 152 if ($currentAlias !== $possibleAlias) {
Chris@18 153 continue;
Chris@18 154 }
Chris@18 155 $this->addBaseName($currentBaseName);
Chris@18 156 return $currentBaseName;
Chris@18 157 }
Chris@18 158
Chris@18 159 return null;
Chris@18 160 }
Chris@18 161
Chris@18 162 /**
Chris@18 163 * @param string $path
Chris@18 164 * @return null|string
Chris@18 165 */
Chris@18 166 private function resolvePossibleAlias($path)
Chris@18 167 {
Chris@18 168 $normalizedPath = Helper::normalizePath($path);
Chris@18 169 return strstr($normalizedPath, '/', true) ?: null;
Chris@18 170 }
Chris@18 171
Chris@18 172 /**
Chris@18 173 * @param string $baseName
Chris@18 174 * @return null|PharInvocation
Chris@18 175 */
Chris@18 176 private function findByBaseName($baseName)
Chris@18 177 {
Chris@18 178 return Manager::instance()->getCollection()->findByCallback(
Chris@18 179 function (PharInvocation $candidate) use ($baseName) {
Chris@18 180 return $candidate->getBaseName() === $baseName;
Chris@18 181 },
Chris@18 182 true
Chris@18 183 );
Chris@18 184 }
Chris@18 185
Chris@18 186 /**
Chris@18 187 * @param string $path
Chris@18 188 * @return null|string
Chris@18 189 */
Chris@18 190 private function findInBaseNames($path)
Chris@18 191 {
Chris@18 192 // return directly if the resolved base name was submitted
Chris@18 193 if (in_array($path, $this->baseNames, true)) {
Chris@18 194 return $path;
Chris@18 195 }
Chris@18 196
Chris@18 197 $parts = explode('/', Helper::normalizePath($path));
Chris@18 198
Chris@18 199 while (count($parts)) {
Chris@18 200 $currentPath = implode('/', $parts);
Chris@18 201 if (isset($this->baseNames[$currentPath])) {
Chris@18 202 return $currentPath;
Chris@18 203 }
Chris@18 204 array_pop($parts);
Chris@18 205 }
Chris@18 206
Chris@18 207 return null;
Chris@18 208 }
Chris@18 209
Chris@18 210 /**
Chris@18 211 * @param string $baseName
Chris@18 212 */
Chris@18 213 private function addBaseName($baseName)
Chris@18 214 {
Chris@18 215 if (isset($this->baseNames[$baseName])) {
Chris@18 216 return;
Chris@18 217 }
Chris@18 218 $this->baseNames[$baseName] = realpath($baseName);
Chris@18 219 }
Chris@18 220
Chris@18 221 /**
Chris@18 222 * Finds confirmed(!) invocations by alias.
Chris@18 223 *
Chris@18 224 * @param string $path
Chris@18 225 * @return null|PharInvocation
Chris@18 226 * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
Chris@18 227 */
Chris@18 228 private function findByAlias($path)
Chris@18 229 {
Chris@18 230 $possibleAlias = $this->resolvePossibleAlias($path);
Chris@18 231 if ($possibleAlias === null) {
Chris@18 232 return null;
Chris@18 233 }
Chris@18 234 return Manager::instance()->getCollection()->findByCallback(
Chris@18 235 function (PharInvocation $candidate) use ($possibleAlias) {
Chris@18 236 return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias;
Chris@18 237 },
Chris@18 238 true
Chris@18 239 );
Chris@18 240 }
Chris@18 241 }