Complex classes like FileAdapterStrategy 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 FileAdapterStrategy, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 63 | class FileAdapterStrategy extends AbstractAdapterStrategy |
||
| 64 | { |
||
| 65 | /** |
||
| 66 | * Adapter strategy type |
||
| 67 | * |
||
| 68 | * @var string |
||
| 69 | */ |
||
| 70 | const TYPE = 'file'; |
||
| 71 | /** |
||
| 72 | * Configuration |
||
| 73 | * |
||
| 74 | * @var array |
||
| 75 | */ |
||
| 76 | protected $config = null; |
||
| 77 | /** |
||
| 78 | * Root directory (without trailing directory separator) |
||
| 79 | * |
||
| 80 | * @var string |
||
| 81 | */ |
||
| 82 | protected $root = null; |
||
| 83 | /** |
||
| 84 | * Configuration directory (including trailing directory separator) |
||
| 85 | * |
||
| 86 | * @var string |
||
| 87 | */ |
||
| 88 | protected $configDir = null; |
||
| 89 | |||
| 90 | /** |
||
| 91 | * Adapter strategy constructor |
||
| 92 | * |
||
| 93 | * @param array $config Adapter strategy configuration |
||
| 94 | * @throws InvalidArgumentException If the root directory configuration is empty |
||
| 95 | * @throws InvalidArgumentException If the root directory configuration is invalid |
||
| 96 | */ |
||
| 97 | 17 | public function __construct(array $config) |
|
| 133 | |||
| 134 | /** |
||
| 135 | * Initialize the repository |
||
| 136 | * |
||
| 137 | * @return boolean Success |
||
| 138 | * @throws RuntimeException If the repository cannot be initialized |
||
| 139 | * @throws RuntimeException If the repository size descriptor can not be created |
||
| 140 | */ |
||
| 141 | 5 | public function initializeRepository() |
|
| 165 | |||
| 166 | /** |
||
| 167 | * Find objects by selector |
||
| 168 | * |
||
| 169 | * @param Selector|SelectorInterface $selector Object selector |
||
| 170 | * @param RepositoryInterface $repository Object repository |
||
| 171 | * @return PathInterface[] Object paths |
||
| 172 | */ |
||
| 173 | 7 | public function findObjectPaths(SelectorInterface $selector, RepositoryInterface $repository) |
|
| 230 | |||
| 231 | /** |
||
| 232 | * Find and return an object resource |
||
| 233 | * |
||
| 234 | * @param string $resourcePath Repository relative resource path |
||
| 235 | * @return ResourceInterface Object resource |
||
| 236 | */ |
||
| 237 | 24 | public function getObjectResource($resourcePath) |
|
| 241 | |||
| 242 | /** |
||
| 243 | * Allocate an object ID and create an object resource |
||
| 244 | * |
||
| 245 | * @param \Closure $creator Object creation closure |
||
| 246 | * @return ObjectInterface Object |
||
| 247 | * @throws RuntimeException If no object could be created |
||
| 248 | * @throws \Exception If another error occurs |
||
| 249 | */ |
||
| 250 | 2 | public function createObjectResource(\Closure $creator) |
|
| 301 | |||
| 302 | /** |
||
| 303 | * Persist an object in the repository |
||
| 304 | * |
||
| 305 | * @param ObjectInterface $object Object |
||
| 306 | * @return AdapterStrategyInterface Self reference |
||
| 307 | */ |
||
| 308 | 1 | public function persistObject(ObjectInterface $object) |
|
| 309 | { |
||
| 310 | // If the object has just been published |
||
| 311 | 1 | if ($object->isPublished()) { |
|
| 312 | $this->publishObject($object); |
||
| 313 | } |
||
| 314 | |||
| 315 | /** @var \Apparat\Object\Infrastructure\Model\Object\Resource $objectResource */ |
||
| 316 | 1 | $objectResource = ResourceFactory::createFromObject($object); |
|
| 317 | |||
| 318 | // Create the absolute object resource path |
||
| 319 | 1 | $objectResourcePath = $this->absoluteResourcePath($object->getRepositoryPath()); |
|
| 320 | |||
| 321 | /** @var Writer $fileWriter */ |
||
| 322 | 1 | $fileWriter = Kernel::create( |
|
| 323 | 1 | Writer::class, |
|
| 324 | 1 | [$objectResourcePath, Writer::FILE_CREATE | Writer::FILE_CREATE_DIRS | Writer::FILE_OVERWRITE] |
|
| 325 | 1 | ); |
|
| 326 | 1 | $objectResource->dump($fileWriter); |
|
| 327 | |||
| 328 | 1 | return $this; |
|
| 329 | } |
||
| 330 | |||
| 331 | /** |
||
| 332 | * Publish an object in the repository |
||
| 333 | * |
||
| 334 | * @param ObjectInterface $object |
||
| 335 | */ |
||
| 336 | protected function publishObject(ObjectInterface $object) |
||
| 337 | { |
||
| 338 | $objectRepositoryPath = $object->getRepositoryPath(); |
||
| 339 | |||
| 340 | // If the object had been persisted as a draft: Remove the draft resource |
||
| 341 | $objectDraftResPath = $this->absoluteResourcePath($objectRepositoryPath->setDraft(true)); |
||
| 342 | if (@file_exists($objectDraftResPath)) { |
||
| 343 | unlink($objectDraftResPath); |
||
| 344 | } |
||
| 345 | |||
| 346 | // If it's not the first object revision: Rotate the previous revision resource |
||
| 347 | $objectRevisionNumber = $object->getRevision()->getRevision(); |
||
| 348 | if ($objectRevisionNumber > 1) { |
||
| 349 | // Build the "current" object repository path |
||
| 350 | $currentRevision = Revision::current(); |
||
| 351 | $curObjectResPath = |
||
| 352 | $this->absoluteResourcePath($objectRepositoryPath->setRevision($currentRevision)); |
||
| 353 | |||
| 354 | // Build the previous object repository path |
||
| 355 | /** @var Revision $previousRevision */ |
||
| 356 | $previousRevision = Kernel::create(Revision::class, [$objectRevisionNumber - 1]); |
||
| 357 | $prevObjectResPath |
||
| 358 | = $this->absoluteResourcePath($objectRepositoryPath->setRevision($previousRevision)); |
||
| 359 | |||
| 360 | // Rotate the previous revision's resource path |
||
| 361 | if (file_exists($curObjectResPath)) { |
||
| 362 | rename($curObjectResPath, $prevObjectResPath); |
||
| 363 | } |
||
| 364 | } |
||
| 365 | } |
||
| 366 | |||
| 367 | /** |
||
| 368 | * Build an absolute repository resource path |
||
| 369 | * |
||
| 370 | * @param RepositoryPathInterface $repositoryPath Repository path |
||
| 371 | * @return string Absolute repository resource path |
||
| 372 | */ |
||
| 373 | 1 | protected function absoluteResourcePath(RepositoryPathInterface $repositoryPath) |
|
| 381 | |||
| 382 | /** |
||
| 383 | * Return the repository size (number of objects in the repository) |
||
| 384 | * |
||
| 385 | * @return int Repository size |
||
| 386 | */ |
||
| 387 | 2 | public function getRepositorySize() |
|
| 396 | } |
||
| 397 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.