[TASK] Use $x[n] instead of substr($x, n, 1)
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Resources / PHP / TYPO3.Flow / Classes / TYPO3 / Flow / Package / Package.php
1 <?php
2 namespace TYPO3\Flow\Package;
3
4 /* *
5 * This script belongs to the TYPO3 Flow framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 use TYPO3\Flow\Utility\Files;
15
16 /**
17 * A Package
18 *
19 * @api
20 */
21 class Package implements PackageInterface {
22
23 /**
24 * Unique key of this package. Example for the Flow package: "TYPO3.Flow"
25 * @var string
26 */
27 protected $packageKey;
28
29 /**
30 * @var string
31 */
32 protected $manifestPath;
33
34 /**
35 * Full path to this package's main directory
36 * @var string
37 */
38 protected $packagePath;
39
40 /**
41 * Full path to this package's PSR-0 class loader entry point
42 * @var string
43 */
44 protected $classesPath;
45
46 /**
47 * If this package is protected and therefore cannot be deactivated or deleted
48 * @var boolean
49 * @api
50 */
51 protected $protected = FALSE;
52
53 /**
54 * @var \stdClass
55 */
56 protected $composerManifest;
57
58 /**
59 * Meta information about this package
60 * @var \TYPO3\Flow\Package\MetaData
61 */
62 protected $packageMetaData;
63
64 /**
65 * Names and relative paths (to this package directory) of files containing classes
66 * @var array
67 */
68 protected $classFiles;
69
70 /**
71 * The namespace of the classes contained in this package
72 * @var string
73 */
74 protected $namespace;
75
76 /**
77 * If enabled, the files in the Classes directory are registered and Reflection, Dependency Injection, AOP etc. are supported.
78 * Disable this flag if you don't need object management for your package and want to save some memory.
79 * @var boolean
80 * @api
81 */
82 protected $objectManagementEnabled = TRUE;
83
84 /**
85 * @var \TYPO3\Flow\Package\PackageManager
86 */
87 protected $packageManager;
88
89 /**
90 * Constructor
91 *
92 * @param \TYPO3\Flow\Package\PackageManager $packageManager the package manager which knows this package
93 * @param string $packageKey Key of this package
94 * @param string $packagePath Absolute path to the location of the package's composer manifest
95 * @param string $classesPath Path the classes of the package are in, relative to $packagePath. Optional, read from Composer manifest if not set.
96 * @param string $manifestPath Path the composer manifest of the package, relative to $packagePath. Optional, defaults to ''.
97 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageKeyException if an invalid package key was passed
98 * @throws \TYPO3\Flow\Package\Exception\InvalidPackagePathException if an invalid package path was passed
99 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageManifestException if no composer manifest file could be found
100 */
101 public function __construct(\TYPO3\Flow\Package\PackageManager $packageManager, $packageKey, $packagePath, $classesPath = NULL, $manifestPath = '') {
102 if (preg_match(self::PATTERN_MATCH_PACKAGEKEY, $packageKey) !== 1) {
103 throw new \TYPO3\Flow\Package\Exception\InvalidPackageKeyException('"' . $packageKey . '" is not a valid package key.', 1217959510);
104 }
105 if (!(is_dir($packagePath) || (Files::is_link($packagePath) && is_dir(Files::getNormalizedPath($packagePath))))) {
106 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('Tried to instantiate a package object for package "%s" with a non-existing package path "%s". Either the package does not exist anymore, or the code creating this object contains an error.', $packageKey, $packagePath), 1166631889);
107 }
108 if (substr($packagePath, -1, 1) !== '/') {
109 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package path "%s" provided for package "%s" has no trailing forward slash.', $packagePath, $packageKey), 1166633720);
110 }
111 if ($classesPath[1] === '/') {
112 throw new \TYPO3\Flow\Package\Exception\InvalidPackagePathException(sprintf('The package classes path provided for package "%s" has a leading forward slash.', $packageKey), 1334841320);
113 }
114 if (!file_exists($packagePath . $manifestPath . 'composer.json')) {
115 throw new \TYPO3\Flow\Package\Exception\InvalidPackageManifestException(sprintf('No composer manifest file found for package "%s". Please create one at "%scomposer.json".', $packageKey, $manifestPath), 1349776393);
116 }
117
118 $this->packageManager = $packageManager;
119 $this->manifestPath = $manifestPath;
120 $this->packageKey = $packageKey;
121 $this->packagePath = Files::getNormalizedPath($packagePath);
122 if (isset($this->getComposerManifest()->autoload->{'psr-0'})) {
123 $this->classesPath = Files::getNormalizedPath($this->packagePath . $this->getComposerManifest()->autoload->{'psr-0'}->{$this->getNamespace()});
124 } else {
125 $this->classesPath = Files::getNormalizedPath($this->packagePath . $classesPath);
126 }
127 }
128
129 /**
130 * Invokes custom PHP code directly after the package manager has been initialized.
131 *
132 * @param \TYPO3\Flow\Core\Bootstrap $bootstrap The current bootstrap
133 * @return void
134 */
135 public function boot(\TYPO3\Flow\Core\Bootstrap $bootstrap) {
136 }
137
138 /**
139 * Returns the package meta data object of this package.
140 *
141 * @return \TYPO3\Flow\Package\MetaData
142 */
143 public function getPackageMetaData() {
144 if ($this->packageMetaData === NULL) {
145 $this->packageMetaData = new MetaData($this->getPackageKey());
146 $this->packageMetaData->setDescription($this->getComposerManifest('description'));
147 $this->packageMetaData->setVersion($this->getComposerManifest('version'));
148 $requirements = $this->getComposerManifest('require');
149 if ($requirements !== NULL) {
150 foreach ($requirements as $requirement => $version) {
151 if ($this->packageRequirementIsComposerPackage($requirement) === FALSE) {
152 // Skip non-package requirements
153 continue;
154 }
155 $packageKey = $this->packageManager->getPackageKeyFromComposerName($requirement);
156 $constraint = new MetaData\PackageConstraint(MetaDataInterface::CONSTRAINT_TYPE_DEPENDS, $packageKey);
157 $this->packageMetaData->addConstraint($constraint);
158 }
159 }
160
161 }
162 return $this->packageMetaData;
163 }
164
165 /**
166 * Check whether the given package requirement (like "typo3/flow" or "php") is a composer package or not
167 *
168 * @param string $requirement the composer requirement string
169 * @return boolean TRUE if $requirement is a composer package (contains a slash), FALSE otherwise
170 */
171 protected function packageRequirementIsComposerPackage($requirement) {
172 return (strpos($requirement, '/') !== FALSE);
173 }
174
175 /**
176 * Returns the array of filenames of the class files
177 *
178 * @return array An array of class names (key) and their filename, including the relative path to the package's directory
179 */
180 public function getClassFiles() {
181 if (!is_array($this->classFiles)) {
182 $this->classFiles = $this->buildArrayOfClassFiles($this->classesPath);
183 }
184 return $this->classFiles;
185 }
186
187 /**
188 * Returns the array of filenames of class files provided by functional tests contained in this package
189 *
190 * @return array An array of class names (key) and their filename, including the relative path to the package's directory
191 */
192 public function getFunctionalTestsClassFiles() {
193 return $this->buildArrayOfClassFiles($this->packagePath . self::DIRECTORY_TESTS_FUNCTIONAL, $this->getNamespace() . '\\Tests\\Functional\\');
194 }
195
196 /**
197 * Returns the package key of this package.
198 *
199 * @return string
200 * @api
201 */
202 public function getPackageKey() {
203 return $this->packageKey;
204 }
205
206 /**
207 * Returns the PHP namespace of classes in this package.
208 *
209 * @return string
210 * @throws \TYPO3\Flow\Package\Exception\InvalidPackageStateException
211 * @api
212 */
213 public function getNamespace() {
214 if (!$this->namespace) {
215 $manifest = $this->getComposerManifest();
216 if (isset($manifest->autoload->{'psr-0'})) {
217 $namespaces = $manifest->autoload->{'psr-0'};
218 if (count($namespaces) === 1) {
219 $namespace = key($namespaces);
220 } else {
221 throw new \TYPO3\Flow\Package\Exception\InvalidPackageStateException(sprintf('The Composer manifest of package "%s" contains multiple namespace definitions in its autoload section but Flow does only support one namespace per package.', $this->packageKey), 1348053245);
222 }
223 } else {
224 $namespace = str_replace('.', '\\', $this->getPackageKey());
225 }
226 $this->namespace = $namespace;
227 }
228 return $this->namespace;
229 }
230
231 /**
232 * Tells if this package is protected and therefore cannot be deactivated or deleted
233 *
234 * @return boolean
235 * @api
236 */
237 public function isProtected() {
238 return $this->protected;
239 }
240
241 /**
242 * Tells if files in the Classes directory should be registered and object management enabled for this package.
243 *
244 * @return boolean
245 */
246 public function isObjectManagementEnabled() {
247 return $this->objectManagementEnabled;
248 }
249
250 /**
251 * Sets the protection flag of the package
252 *
253 * @param boolean $protected TRUE if the package should be protected, otherwise FALSE
254 * @return void
255 * @api
256 */
257 public function setProtected($protected) {
258 $this->protected = (boolean)$protected;
259 }
260
261 /**
262 * Returns the full path to this package's main directory
263 *
264 * @return string Path to this package's main directory
265 * @api
266 */
267 public function getPackagePath() {
268 return $this->packagePath;
269 }
270
271 /**
272 * Returns the full path to the packages Composer manifest
273 *
274 * @return string
275 */
276 public function getManifestPath() {
277 return $this->packagePath . $this->manifestPath;
278 }
279
280 /**
281 * Returns the full path to this package's Classes directory
282 *
283 * @return string Path to this package's Classes directory
284 * @api
285 */
286 public function getClassesPath() {
287 return $this->classesPath;
288 }
289
290 /**
291 * Returns the full path to the package's classes namespace entry path,
292 * e.g. "My.Package/ClassesPath/My/Package/"
293 *
294 * @return string Path to this package's Classes directory
295 * @api
296 */
297 public function getClassesNamespaceEntryPath() {
298 $pathifiedNamespace = str_replace('\\', '/', $this->getNamespace());
299 return Files::getNormalizedPath($this->classesPath . trim($pathifiedNamespace, '/'));
300 }
301
302 /**
303 * Returns the full path to this package's functional tests directory
304 *
305 * @return string Path to this package's functional tests directory
306 * @api
307 */
308 public function getFunctionalTestsPath() {
309 return $this->packagePath . self::DIRECTORY_TESTS_FUNCTIONAL;
310 }
311
312 /**
313 * Returns the full path to this package's Resources directory
314 *
315 * @return string Path to this package's Resources directory
316 * @api
317 */
318 public function getResourcesPath() {
319 return $this->packagePath . self::DIRECTORY_RESOURCES;
320 }
321
322 /**
323 * Returns the full path to this package's Configuration directory
324 *
325 * @return string Path to this package's Configuration directory
326 * @api
327 */
328 public function getConfigurationPath() {
329 return $this->packagePath . self::DIRECTORY_CONFIGURATION;
330 }
331
332 /**
333 * Returns the full path to the package's meta data directory
334 *
335 * @return string Full path to the package's meta data directory
336 * @api
337 */
338 public function getMetaPath() {
339 return $this->packagePath . self::DIRECTORY_METADATA;
340 }
341
342 /**
343 * Returns the full path to the package's documentation directory
344 *
345 * @return string Full path to the package's documentation directory
346 * @api
347 */
348 public function getDocumentationPath() {
349 return $this->packagePath . self::DIRECTORY_DOCUMENTATION;
350 }
351
352 /**
353 * Returns the available documentations for this package
354 *
355 * @return array Array of \TYPO3\Flow\Package\Documentation
356 * @api
357 */
358 public function getPackageDocumentations() {
359 $documentations = array();
360 $documentationPath = $this->getDocumentationPath();
361 if (is_dir($documentationPath)) {
362 $documentationsDirectoryIterator = new \DirectoryIterator($documentationPath);
363 $documentationsDirectoryIterator->rewind();
364 while ($documentationsDirectoryIterator->valid()) {
365 $filename = $documentationsDirectoryIterator->getFilename();
366 if ($filename[0] != '.' && $documentationsDirectoryIterator->isDir()) {
367 $filename = $documentationsDirectoryIterator->getFilename();
368 $documentation = new \TYPO3\Flow\Package\Documentation($this, $filename, $documentationPath . $filename . '/');
369 $documentations[$filename] = $documentation;
370 }
371 $documentationsDirectoryIterator->next();
372 }
373 }
374 return $documentations;
375 }
376
377 /**
378 * Returns contents of Composer manifest - or part there of.
379 *
380 * @param string $key Optional. Only return the part of the manifest indexed by 'key'
381 * @return mixed|NULL
382 * @see json_decode for return values
383 */
384 public function getComposerManifest($key = NULL) {
385 if (!isset($this->composerManifest)) {
386 $this->composerManifest = PackageManager::getComposerManifest($this->getManifestPath());
387 }
388
389 return PackageManager::getComposerManifest($this->getManifestPath(), $key, $this->composerManifest);
390 }
391
392 /**
393 * Builds and returns an array of class names => file names of all
394 * *.php files in the package's Classes directory and its sub-
395 * directories.
396 *
397 * @param string $classesPath Base path acting as the parent directory for potential class files
398 * @param string $extraNamespaceSegment A PHP class namespace segment which should be inserted like so: \TYPO3\PackageKey\{namespacePrefix\}PathSegment\PathSegment\Filename
399 * @param string $subDirectory Used internally
400 * @param integer $recursionLevel Used internally
401 * @return array
402 * @throws \TYPO3\Flow\Package\Exception if recursion into directories was too deep or another error occurred
403 */
404 protected function buildArrayOfClassFiles($classesPath, $extraNamespaceSegment = '', $subDirectory = '', $recursionLevel = 0) {
405 $classFiles = array();
406 $currentPath = $classesPath . $subDirectory;
407 $currentRelativePath = substr($currentPath, strlen($this->packagePath));
408
409 if (!is_dir($currentPath)) {
410 return array();
411 }
412 if ($recursionLevel > 100) {
413 throw new \TYPO3\Flow\Package\Exception('Recursion too deep.', 1166635495);
414 }
415
416 try {
417 $classesDirectoryIterator = new \DirectoryIterator($currentPath);
418 while ($classesDirectoryIterator->valid()) {
419 $filename = $classesDirectoryIterator->getFilename();
420 if ($filename[0] != '.') {
421 if (is_dir($currentPath . $filename)) {
422 $classFiles = array_merge($classFiles, $this->buildArrayOfClassFiles($classesPath, $extraNamespaceSegment, $subDirectory . $filename . '/', ($recursionLevel + 1)));
423 } else {
424 if (substr($filename, -4, 4) === '.php') {
425 $className = (str_replace('/', '\\', ($extraNamespaceSegment . substr($currentPath, strlen($classesPath)) . substr($filename, 0, -4))));
426 $classFiles[$className] = $currentRelativePath . $filename;
427 }
428 }
429 }
430 $classesDirectoryIterator->next();
431 }
432
433 } catch (\Exception $exception) {
434 throw new \TYPO3\Flow\Package\Exception($exception->getMessage(), 1166633720);
435 }
436 return $classFiles;
437 }
438
439 /**
440 * Added by TYPO3 CMS
441 *
442 * The package caching serializes package objects.
443 * The package manager instance may not be serialized
444 * as a fresh instance is created upon every request.
445 *
446 * This method will be removed once the package is
447 * released of the package manager dependency.
448 *
449 * @return array
450 */
451 public function __sleep() {
452 $properties = get_class_vars(__CLASS__);
453 unset($properties['packageManager']);
454 return array_keys($properties);
455 }
456
457 /**
458 * Added by TYPO3 CMS
459 *
460 * The package caching deserializes package objects.
461 * A fresh package manager instance has to be set
462 * during bootstrapping.
463 *
464 * This method will be removed once the package is
465 * released of the package manager dependency.
466 */
467 public function __wakeup() {
468 if (isset($GLOBALS['TYPO3_currentPackageManager'])) {
469 $this->packageManager = $GLOBALS['TYPO3_currentPackageManager'];
470 }
471 }
472 }
473
474 ?>