Total Complexity | 67 |
Total Lines | 484 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like StorageRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use StorageRepository, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
37 | class StorageRepository implements LoggerAwareInterface |
||
38 | { |
||
39 | use LoggerAwareTrait; |
||
40 | |||
41 | /** |
||
42 | * @var array|null |
||
43 | */ |
||
44 | protected $storageRowCache; |
||
45 | |||
46 | /** |
||
47 | * @var array<int, LocalPath>|null |
||
48 | */ |
||
49 | protected $localDriverStorageCache; |
||
50 | |||
51 | /** |
||
52 | * @var string |
||
53 | */ |
||
54 | protected $table = 'sys_file_storage'; |
||
55 | |||
56 | /** |
||
57 | * @var DriverRegistry |
||
58 | */ |
||
59 | protected $driverRegistry; |
||
60 | |||
61 | /** |
||
62 | * @var EventDispatcherInterface |
||
63 | */ |
||
64 | protected $eventDispatcher; |
||
65 | |||
66 | /** |
||
67 | * @var ResourceStorage[]|null |
||
68 | */ |
||
69 | protected $storageInstances; |
||
70 | |||
71 | public function __construct(EventDispatcherInterface $eventDispatcher, DriverRegistry $driverRegistry) |
||
72 | { |
||
73 | $this->eventDispatcher = $eventDispatcher; |
||
74 | $this->driverRegistry = $driverRegistry; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Returns the Default Storage |
||
79 | * |
||
80 | * The Default Storage is considered to be the replacement for the fileadmin/ construct. |
||
81 | * It is automatically created with the setting fileadminDir from install tool. |
||
82 | * getDefaultStorage->getDefaultFolder() will get you fileadmin/user_upload/ in a standard |
||
83 | * TYPO3 installation. |
||
84 | * |
||
85 | * @return ResourceStorage|null |
||
86 | */ |
||
87 | public function getDefaultStorage(): ?ResourceStorage |
||
88 | { |
||
89 | $allStorages = $this->findAll(); |
||
90 | foreach ($allStorages as $storage) { |
||
91 | if ($storage->isDefault()) { |
||
92 | return $storage; |
||
93 | } |
||
94 | } |
||
95 | return null; |
||
96 | } |
||
97 | |||
98 | public function findByUid(int $uid): ?ResourceStorage |
||
99 | { |
||
100 | $this->initializeLocalCache(); |
||
101 | if (isset($this->storageRowCache[$uid]) || $uid === 0) { |
||
102 | return $this->getStorageObject($uid, $this->storageRowCache[$uid] ?? []); |
||
103 | } |
||
104 | return null; |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Gets a storage object from a combined identifier |
||
109 | * |
||
110 | * @param string $identifier An identifier of the form [storage uid]:[object identifier] |
||
111 | * @return ResourceStorage|null |
||
112 | */ |
||
113 | public function findByCombinedIdentifier(string $identifier): ?ResourceStorage |
||
114 | { |
||
115 | $parts = GeneralUtility::trimExplode(':', $identifier); |
||
116 | return count($parts) === 2 ? $this->findByUid((int)$parts[0]) : null; |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * @param int $uid |
||
121 | * @return array |
||
122 | */ |
||
123 | protected function fetchRecordDataByUid(int $uid): array |
||
124 | { |
||
125 | $this->initializeLocalCache(); |
||
126 | if (!isset($this->storageRowCache[$uid])) { |
||
127 | throw new \InvalidArgumentException(sprintf('No storage found with uid "%d".', $uid), 1599235454); |
||
128 | } |
||
129 | |||
130 | return $this->storageRowCache[$uid]; |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Initializes the Storage |
||
135 | */ |
||
136 | protected function initializeLocalCache() |
||
178 | } |
||
179 | } |
||
180 | } |
||
181 | } |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Flush the internal storage caches to force reloading of storages with the next fetch. |
||
186 | * |
||
187 | * @internal |
||
188 | */ |
||
189 | public function flush(): void |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Finds storages by type, i.e. the driver used |
||
198 | * |
||
199 | * @param string $storageType |
||
200 | * @return ResourceStorage[] |
||
201 | */ |
||
202 | public function findByStorageType($storageType) |
||
203 | { |
||
204 | $this->initializeLocalCache(); |
||
205 | |||
206 | $storageObjects = []; |
||
207 | foreach ($this->storageRowCache as $storageRow) { |
||
208 | if ($storageRow['driver'] !== $storageType) { |
||
209 | continue; |
||
210 | } |
||
211 | if ($this->driverRegistry->driverExists($storageRow['driver'])) { |
||
212 | $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow); |
||
213 | } else { |
||
214 | $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]); |
||
|
|||
215 | } |
||
216 | } |
||
217 | return $storageObjects; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Returns a list of mountpoints that are available in the VFS. |
||
222 | * In case no storage exists this automatically created a storage for fileadmin/ |
||
223 | * |
||
224 | * @return ResourceStorage[] |
||
225 | */ |
||
226 | public function findAll() |
||
227 | { |
||
228 | $this->initializeLocalCache(); |
||
229 | |||
230 | $storageObjects = []; |
||
231 | foreach ($this->storageRowCache as $storageRow) { |
||
232 | if ($this->driverRegistry->driverExists($storageRow['driver'])) { |
||
233 | $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow); |
||
234 | } else { |
||
235 | $this->logger->warning('Could not instantiate storage "{name}" because of missing driver.', ['name' => $storageRow['name']]); |
||
236 | } |
||
237 | } |
||
238 | return $storageObjects; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Create the initial local storage base e.g. for the fileadmin/ directory. |
||
243 | * |
||
244 | * @param string $name |
||
245 | * @param string $basePath |
||
246 | * @param string $pathType |
||
247 | * @param string $description |
||
248 | * @param bool $default set to default storage |
||
249 | * @return int uid of the inserted record |
||
250 | */ |
||
251 | public function createLocalStorage($name, $basePath, $pathType, $description = '', $default = false) |
||
252 | { |
||
253 | $caseSensitive = $this->testCaseSensitivity($pathType === 'relative' ? Environment::getPublicPath() . '/' . $basePath : $basePath); |
||
254 | // create the FlexForm for the driver configuration |
||
255 | $flexFormData = [ |
||
256 | 'data' => [ |
||
257 | 'sDEF' => [ |
||
258 | 'lDEF' => [ |
||
259 | 'basePath' => ['vDEF' => rtrim($basePath, '/') . '/'], |
||
260 | 'pathType' => ['vDEF' => $pathType], |
||
261 | 'caseSensitive' => ['vDEF' => $caseSensitive] |
||
262 | ] |
||
263 | ] |
||
264 | ] |
||
265 | ]; |
||
266 | |||
267 | $flexFormXml = GeneralUtility::makeInstance(FlexFormTools::class)->flexArray2Xml($flexFormData, true); |
||
268 | |||
269 | // create the record |
||
270 | $field_values = [ |
||
271 | 'pid' => 0, |
||
272 | 'tstamp' => $GLOBALS['EXEC_TIME'], |
||
273 | 'crdate' => $GLOBALS['EXEC_TIME'], |
||
274 | 'name' => $name, |
||
275 | 'description' => $description, |
||
276 | 'driver' => 'Local', |
||
277 | 'configuration' => $flexFormXml, |
||
278 | 'is_online' => 1, |
||
279 | 'is_browsable' => 1, |
||
280 | 'is_public' => 1, |
||
281 | 'is_writable' => 1, |
||
282 | 'is_default' => $default ? 1 : 0 |
||
283 | ]; |
||
284 | |||
285 | $dbConnection = GeneralUtility::makeInstance(ConnectionPool::class) |
||
286 | ->getConnectionForTable($this->table); |
||
287 | $dbConnection->insert($this->table, $field_values); |
||
288 | |||
289 | // Flush local resourceStorage cache so the storage can be accessed during the same request right away |
||
290 | $this->flush(); |
||
291 | |||
292 | return (int)$dbConnection->lastInsertId($this->table); |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Test if the local filesystem is case sensitive |
||
297 | * |
||
298 | * @param string $absolutePath |
||
299 | * @return bool |
||
300 | */ |
||
301 | protected function testCaseSensitivity($absolutePath) |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Creates an instance of the storage from given UID. The $recordData can |
||
327 | * be supplied to increase performance. |
||
328 | * |
||
329 | * @param int $uid The uid of the storage to instantiate. |
||
330 | * @param array $recordData The record row from database. |
||
331 | * @param mixed|null $fileIdentifier Identifier for a file. Used for auto-detection of a storage, but only if $uid === 0 (Local default storage) is used |
||
332 | * @throws \InvalidArgumentException |
||
333 | * @return ResourceStorage |
||
334 | */ |
||
335 | public function getStorageObject($uid, array $recordData = [], &$fileIdentifier = null): ResourceStorage |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Checks whether a file resides within a real storage in local file system. |
||
386 | * If no match is found, uid 0 is returned which is a fallback storage pointing to fileadmin in public web path. |
||
387 | * |
||
388 | * The file identifier is adapted accordingly to match the new storage's base path. |
||
389 | * |
||
390 | * @param string $localPath |
||
391 | * @return int |
||
392 | */ |
||
393 | protected function findBestMatchingStorageByLocalPath(&$localPath): int |
||
394 | { |
||
395 | if ($this->localDriverStorageCache === null) { |
||
396 | $this->initializeLocalStorageCache(); |
||
397 | } |
||
398 | // normalize path information (`//`, `../`) |
||
399 | $localPath = PathUtility::getCanonicalPath($localPath); |
||
400 | if ($localPath[0] !== '/') { |
||
401 | $localPath = '/' . $localPath; |
||
402 | } |
||
403 | $bestMatchStorageUid = 0; |
||
404 | $bestMatchLength = 0; |
||
405 | foreach ($this->localDriverStorageCache as $storageUid => $basePath) { |
||
406 | // try to match (resolved) relative base-path |
||
407 | if ($basePath->getRelative() !== null |
||
408 | && null !== $commonPrefix = PathUtility::getCommonPrefix([$basePath->getRelative(), $localPath]) |
||
409 | ) { |
||
410 | $matchLength = strlen($commonPrefix); |
||
411 | $basePathLength = strlen($basePath->getRelative()); |
||
412 | if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) { |
||
413 | $bestMatchStorageUid = $storageUid; |
||
414 | $bestMatchLength = $matchLength; |
||
415 | } |
||
416 | } |
||
417 | // try to match (resolved) absolute base-path |
||
418 | if (null !== $commonPrefix = PathUtility::getCommonPrefix([$basePath->getAbsolute(), $localPath])) { |
||
419 | $matchLength = strlen($commonPrefix); |
||
420 | $basePathLength = strlen($basePath->getAbsolute()); |
||
421 | if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) { |
||
422 | $bestMatchStorageUid = $storageUid; |
||
423 | $bestMatchLength = $matchLength; |
||
424 | } |
||
425 | } |
||
426 | } |
||
427 | if ($bestMatchLength > 0) { |
||
428 | // $commonPrefix always has trailing slash, which needs to be excluded |
||
429 | // (commonPrefix: /some/path/, localPath: /some/path/file.png --> /file.png; keep leading slash) |
||
430 | $localPath = substr($localPath, $bestMatchLength - 1); |
||
431 | } |
||
432 | return $bestMatchStorageUid; |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Creates an array mapping all uids to the basePath of storages using the "local" driver. |
||
437 | */ |
||
438 | protected function initializeLocalStorageCache(): void |
||
439 | { |
||
440 | $this->localDriverStorageCache = [ |
||
441 | // implicit legacy storage in project's public path |
||
442 | 0 => new LocalPath('/', LocalPath::TYPE_RELATIVE) |
||
443 | ]; |
||
444 | $storageObjects = $this->findByStorageType('Local'); |
||
445 | foreach ($storageObjects as $localStorage) { |
||
446 | $configuration = $localStorage->getConfiguration(); |
||
447 | if (!isset($configuration['basePath']) || !isset($configuration['pathType'])) { |
||
448 | continue; |
||
449 | } |
||
450 | if ($configuration['pathType'] === 'relative') { |
||
451 | $pathType = LocalPath::TYPE_RELATIVE; |
||
452 | } elseif ($configuration['pathType'] === 'absolute') { |
||
453 | $pathType = LocalPath::TYPE_ABSOLUTE; |
||
454 | } else { |
||
455 | continue; |
||
456 | } |
||
457 | $this->localDriverStorageCache[$localStorage->getUid()] = GeneralUtility::makeInstance( |
||
458 | LocalPath::class, |
||
459 | $configuration['basePath'], |
||
460 | $pathType |
||
461 | ); |
||
462 | } |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Creates a storage object from a storage database row. |
||
467 | * |
||
468 | * @param array $storageRecord |
||
469 | * @param array|null $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead) |
||
470 | * @return ResourceStorage |
||
471 | * @internal this method is only public for having access to ResourceFactory->createStorageObject(). In TYPO3 v12 this method can be changed to protected again. |
||
472 | */ |
||
473 | public function createStorageObject(array $storageRecord, array $storageConfiguration = null): ResourceStorage |
||
474 | { |
||
475 | if (!$storageConfiguration && !empty($storageRecord['configuration'])) { |
||
476 | $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storageRecord['configuration']); |
||
477 | } |
||
478 | $driverType = $storageRecord['driver']; |
||
479 | $driverObject = $this->getDriverObject($driverType, (array)$storageConfiguration); |
||
480 | $storageRecord['configuration'] = $storageConfiguration; |
||
481 | return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->eventDispatcher); |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * Converts a flexform data string to a flat array with key value pairs |
||
486 | * |
||
487 | * @param string $flexFormData |
||
488 | * @return array Array with key => value pairs of the field data in the FlexForm |
||
489 | */ |
||
490 | protected function convertFlexFormDataToConfigurationArray(string $flexFormData): array |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Creates a driver object for a specified storage object. |
||
500 | * |
||
501 | * @param string $driverIdentificationString The driver class (or identifier) to use. |
||
502 | * @param array $driverConfiguration The configuration of the storage |
||
503 | * @return DriverInterface |
||
504 | */ |
||
505 | protected function getDriverObject(string $driverIdentificationString, array $driverConfiguration): DriverInterface |
||
506 | { |
||
507 | $driverClass = $this->driverRegistry->getDriverClass($driverIdentificationString); |
||
508 | /** @var DriverInterface $driverObject */ |
||
509 | $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration); |
||
510 | return $driverObject; |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * @param array $storageRecord |
||
515 | * @return ResourceStorage |
||
516 | * @internal |
||
517 | */ |
||
518 | public function createFromRecord(array $storageRecord): ResourceStorage |
||
521 | } |
||
522 | } |
||
523 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.