Complex classes like Linker 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 Linker, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class Linker implements CompilerPassInterface |
||
47 | { |
||
48 | const COMPILER_PRIORITY = 10000; |
||
49 | |||
50 | const CONTEXT_MARKER = '@context'; |
||
51 | |||
52 | /** @var DescriptorAbstract[] */ |
||
53 | protected $elementList = []; |
||
54 | |||
55 | /** @var string[][] */ |
||
56 | protected $substitutions = []; |
||
57 | |||
58 | /** @var string[] Prevent cycles by tracking which objects have been analyzed */ |
||
59 | protected $processedObjects = []; |
||
60 | |||
61 | 1 | public function getDescription(): string |
|
65 | |||
66 | /** |
||
67 | * Initializes the linker with a series of Descriptors to link to. |
||
68 | * |
||
69 | * @param string[][] $substitutions |
||
70 | */ |
||
71 | 4 | public function __construct(array $substitutions) |
|
75 | |||
76 | 1 | public function execute(ProjectDescriptor $project): void |
|
81 | |||
82 | /** |
||
83 | * Returns the list of substitutions for the linker. |
||
84 | * |
||
85 | * @return string[][] |
||
86 | */ |
||
87 | 1 | public function getSubstitutions(): array |
|
91 | |||
92 | /** |
||
93 | * Sets the list of object aliases to resolve the FQSENs with. |
||
94 | * |
||
95 | * @param DescriptorAbstract[] $elementList |
||
96 | */ |
||
97 | 3 | public function setObjectAliasesList(array $elementList): void |
|
101 | |||
102 | /** |
||
103 | * Substitutes the given item or its children's FQCN with an object alias. |
||
104 | * |
||
105 | * This method may do either of the following depending on the item's type |
||
106 | * |
||
107 | * String |
||
108 | * If the given item is a string then this method will attempt to find an appropriate Class, Interface or |
||
109 | * TraitDescriptor object and return that. See {@see findAlias()} for more information on the normalization |
||
110 | * of these strings. |
||
111 | * |
||
112 | * Array or Traversable |
||
113 | * Iterate through each item, pass each key's contents to a new call to substitute and replace the key's |
||
114 | * contents if the contents is not an object (objects automatically update and saves performance). |
||
115 | * |
||
116 | * Object |
||
117 | * Determines all eligible substitutions using the substitutions property, construct a getter and retrieve |
||
118 | * the field's contents. Pass these contents to a new call of substitute and use a setter to replace the field's |
||
119 | * contents if anything other than null is returned. |
||
120 | * |
||
121 | * This method will return null if no substitution was possible and all of the above should not update the parent |
||
122 | * item when null is passed. |
||
123 | * |
||
124 | * @param string|object|Traversable|array $item |
||
125 | * @param DescriptorAbstract|null $container A descriptor that acts as container for all elements |
||
126 | * underneath or null if there is no current container. |
||
127 | * |
||
128 | * @return null|string|DescriptorAbstract |
||
129 | */ |
||
130 | 7 | public function substitute($item, $container = null) |
|
181 | |||
182 | /** |
||
183 | * Attempts to find a Descriptor object alias with the FQSEN of the element it represents. |
||
184 | * |
||
185 | * This method will try to fetch an element after normalizing the provided FQSEN. The FQSEN may contain references |
||
186 | * (bindings) that can only be resolved during linking (such as `self`) or it may contain a context marker |
||
187 | * {@see CONTEXT_MARKER}. |
||
188 | * |
||
189 | * If there is a context marker then this method will see if a child of the given container exists that matches the |
||
190 | * element following the marker. If such a child does not exist in the current container then the namespace is |
||
191 | * queried if a child exists there that matches. |
||
192 | * |
||
193 | * For example: |
||
194 | * |
||
195 | * Given the Fqsen `@context::myFunction()` and the lastContainer `\My\Class` will this method first check |
||
196 | * to see if `\My\Class::myFunction()` exists; if it doesn't it will then check if `\My\myFunction()` exists. |
||
197 | * |
||
198 | * If neither element exists then this method assumes it is an undocumented class/trait/interface and change the |
||
199 | * given FQSEN by returning the namespaced element name (thus in the example above that would be |
||
200 | * `\My\myFunction()`). The calling method {@see substitute()} will then replace the value of the field containing |
||
201 | * the context marker with this normalized string. |
||
202 | * |
||
203 | * @param DescriptorAbstract|null $container |
||
204 | * |
||
205 | * @return DescriptorAbstract|string|null |
||
206 | */ |
||
207 | 5 | public function findAlias(string $fqsen, $container = null) |
|
239 | |||
240 | /** |
||
241 | * Returns the value of a field in the given object. |
||
242 | * |
||
243 | * @param object $object |
||
244 | * @return string|object |
||
245 | */ |
||
246 | 1 | public function findFieldValue($object, string $fieldName) |
|
252 | |||
253 | /** |
||
254 | * Returns true if the given Descriptor is a container type. |
||
255 | * |
||
256 | * @param DescriptorAbstract|mixed $item |
||
257 | */ |
||
258 | 1 | protected function isDescriptorContainer($item): bool |
|
266 | |||
267 | /** |
||
268 | * Replaces pseudo-types, such as `self`, into a normalized version based on the last container that was |
||
269 | * encountered. |
||
270 | * |
||
271 | * @param DescriptorAbstract|null $container |
||
272 | */ |
||
273 | 2 | protected function replacePseudoTypes(string $fqsen, $container): string |
|
285 | |||
286 | /** |
||
287 | * Returns true if the context marker is found in the given FQSEN. |
||
288 | */ |
||
289 | 4 | protected function isContextMarkerInFqsen(string $fqsen): bool |
|
293 | |||
294 | /** |
||
295 | * Normalizes the given FQSEN as if the context marker represents a class/interface/trait as parent. |
||
296 | */ |
||
297 | 2 | protected function getTypeWithClassAsContext(string $fqsen, DescriptorAbstract $container): string |
|
310 | |||
311 | /** |
||
312 | * Normalizes the given FQSEN as if the context marker represents a class/interface/trait as parent. |
||
313 | */ |
||
314 | 2 | protected function getTypeWithNamespaceAsContext(string $fqsen, DescriptorAbstract $container): string |
|
323 | |||
324 | /** |
||
325 | * Normalizes the given FQSEN as if the context marker represents the global namespace as parent. |
||
326 | */ |
||
327 | 2 | protected function getTypeWithGlobalNamespaceAsContext(string $fqsen): string |
|
331 | |||
332 | /** |
||
333 | * Attempts to find an element with the given Fqsen in the list of elements for this project and returns null if |
||
334 | * it cannot find it. |
||
335 | * |
||
336 | * @return DescriptorAbstract|null |
||
337 | */ |
||
338 | 4 | protected function fetchElementByFqsen(string $fqsen) |
|
342 | } |
||
343 |
Since your code implements the magic getter
_get
, this function will be called for any read access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.If the property has read access only, you can use the @property-read annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.