Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Injector 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 Injector, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
128 | class Injector implements ContainerInterface |
||
129 | { |
||
130 | |||
131 | /** |
||
132 | * Local store of all services |
||
133 | * |
||
134 | * @var array |
||
135 | */ |
||
136 | private $serviceCache; |
||
137 | |||
138 | /** |
||
139 | * Cache of items that need to be mapped for each service that gets injected |
||
140 | * |
||
141 | * @var array |
||
142 | */ |
||
143 | private $injectMap; |
||
144 | |||
145 | /** |
||
146 | * A store of all the service configurations that have been defined. |
||
147 | * |
||
148 | * @var array |
||
149 | */ |
||
150 | private $specs; |
||
151 | |||
152 | /** |
||
153 | * A map of all the properties that should be automagically set on all |
||
154 | * objects instantiated by the injector |
||
155 | */ |
||
156 | private $autoProperties; |
||
157 | |||
158 | /** |
||
159 | * A singleton if you want to use it that way |
||
160 | * |
||
161 | * @var Injector |
||
162 | */ |
||
163 | private static $instance; |
||
164 | |||
165 | /** |
||
166 | * Indicates whether or not to automatically scan properties in injected objects to auto inject |
||
167 | * stuff, similar to the way grails does things. |
||
168 | * |
||
169 | * @var boolean |
||
170 | */ |
||
171 | private $autoScanProperties = false; |
||
172 | |||
173 | /** |
||
174 | * The default factory used to create new instances. |
||
175 | * |
||
176 | * The {@link InjectionCreator} is used by default, which simply directly |
||
177 | * creates objects. This can be changed to use a different default creation |
||
178 | * method if desired. |
||
179 | * |
||
180 | * Each individual component can also specify a custom factory to use by |
||
181 | * using the `factory` parameter. |
||
182 | * |
||
183 | * @var Factory |
||
184 | */ |
||
185 | protected $objectCreator; |
||
186 | |||
187 | /** |
||
188 | * Locator for determining Config properties for services |
||
189 | * |
||
190 | * @var ServiceConfigurationLocator |
||
191 | */ |
||
192 | protected $configLocator; |
||
193 | |||
194 | /** |
||
195 | * Create a new injector. |
||
196 | * |
||
197 | * @param array $config |
||
198 | * Service configuration |
||
199 | */ |
||
200 | public function __construct($config = null) |
||
227 | |||
228 | /** |
||
229 | * The injector instance this one was copied from when Injector::nest() was called. |
||
230 | * |
||
231 | * @var Injector |
||
232 | */ |
||
233 | protected $nestedFrom = null; |
||
234 | |||
235 | /** |
||
236 | * If a user wants to use the injector as a static reference |
||
237 | * |
||
238 | * @param array $config |
||
239 | * @return ContainerInterface |
||
240 | */ |
||
241 | public static function inst($config = null) |
||
248 | |||
249 | /** |
||
250 | * Sets the default global injector instance. |
||
251 | * |
||
252 | * @param ContainerInterface $instance |
||
253 | * @return Injector Reference to new active Injector instance |
||
254 | */ |
||
255 | public static function set_inst(ContainerInterface $instance) |
||
259 | |||
260 | /** |
||
261 | * Make the newly active {@link Injector} be a copy of the current active |
||
262 | * {@link Injector} instance. |
||
263 | * |
||
264 | * You can then make changes to the injector with methods such as |
||
265 | * {@link Injector::inst()->registerService()} which will be discarded |
||
266 | * upon a subsequent call to {@link Injector::unnest()} |
||
267 | * |
||
268 | * @return Injector Reference to new active Injector instance |
||
269 | */ |
||
270 | public static function nest() |
||
278 | |||
279 | /** |
||
280 | * Change the active Injector back to the Injector instance the current active |
||
281 | * Injector object was copied from. |
||
282 | * |
||
283 | * @return Injector Reference to restored active Injector instance |
||
284 | */ |
||
285 | public static function unnest() |
||
297 | |||
298 | /** |
||
299 | * Indicate whether we auto scan injected objects for properties to set. |
||
300 | * |
||
301 | * @param boolean $val |
||
302 | */ |
||
303 | public function setAutoScanProperties($val) |
||
307 | |||
308 | /** |
||
309 | * Sets the default factory to use for creating new objects. |
||
310 | * |
||
311 | * @param \SilverStripe\Core\Injector\Factory $obj |
||
312 | */ |
||
313 | public function setObjectCreator(Factory $obj) |
||
317 | |||
318 | /** |
||
319 | * @return Factory |
||
320 | */ |
||
321 | public function getObjectCreator() |
||
325 | |||
326 | /** |
||
327 | * Set the configuration locator |
||
328 | * @param ServiceConfigurationLocator $configLocator |
||
329 | */ |
||
330 | public function setConfigLocator($configLocator) |
||
334 | |||
335 | /** |
||
336 | * Retrieve the configuration locator |
||
337 | * @return ServiceConfigurationLocator |
||
338 | */ |
||
339 | public function getConfigLocator() |
||
343 | |||
344 | /** |
||
345 | * Add in a specific mapping that should be catered for on a type. |
||
346 | * This allows configuration of what should occur when an object |
||
347 | * of a particular type is injected, and what items should be injected |
||
348 | * for those properties / methods. |
||
349 | * |
||
350 | * @param string $class The class to set a mapping for |
||
351 | * @param string $property The property to set the mapping for |
||
352 | * @param string $toInject The registered type that will be injected |
||
353 | * @param string $injectVia Whether to inject by setting a property or calling a setter |
||
354 | */ |
||
355 | public function setInjectMapping($class, $property, $toInject, $injectVia = 'property') |
||
363 | |||
364 | /** |
||
365 | * Add an object that should be automatically set on managed objects |
||
366 | * |
||
367 | * This allows you to specify, for example, that EVERY managed object |
||
368 | * will be automatically inject with a log object by the following |
||
369 | * |
||
370 | * $injector->addAutoProperty('log', new Logger()); |
||
371 | * |
||
372 | * @param string $property |
||
373 | * the name of the property |
||
374 | * @param object $object |
||
375 | * the object to be set |
||
376 | * @return $this |
||
377 | */ |
||
378 | public function addAutoProperty($property, $object) |
||
383 | |||
384 | /** |
||
385 | * Load services using the passed in configuration for those services |
||
386 | * |
||
387 | * @param array $config |
||
388 | * @return $this |
||
389 | */ |
||
390 | public function load($config = array()) |
||
456 | |||
457 | /** |
||
458 | * Update the configuration of an already defined service |
||
459 | * |
||
460 | * Use this if you don't want to register a complete new config, just append |
||
461 | * to an existing configuration. Helpful to avoid overwriting someone else's changes |
||
462 | * |
||
463 | * updateSpec('RequestProcessor', 'filters', '%$MyFilter') |
||
464 | * |
||
465 | * @param string $id |
||
466 | * The name of the service to update the definition for |
||
467 | * @param string $property |
||
468 | * The name of the property to update. |
||
469 | * @param mixed $value |
||
470 | * The value to set |
||
471 | * @param boolean $append |
||
472 | * Whether to append (the default) when the property is an array |
||
473 | */ |
||
474 | public function updateSpec($id, $property, $value, $append = true) |
||
492 | |||
493 | /** |
||
494 | * Update a class specification to convert constructor configuration information if needed |
||
495 | * |
||
496 | * We do this as a separate process to avoid unneeded calls to convertServiceProperty |
||
497 | * |
||
498 | * @param array $spec |
||
499 | * The class specification to update |
||
500 | */ |
||
501 | protected function updateSpecConstructor(&$spec) |
||
507 | |||
508 | /** |
||
509 | * Recursively convert a value into its proper representation with service references |
||
510 | * resolved to actual objects |
||
511 | * |
||
512 | * @param string $value |
||
513 | * @return array|mixed|string |
||
514 | */ |
||
515 | public function convertServiceProperty($value) |
||
544 | |||
545 | /** |
||
546 | * Instantiate a managed object |
||
547 | * |
||
548 | * Given a specification of the form |
||
549 | * |
||
550 | * array( |
||
551 | * 'class' => 'ClassName', |
||
552 | * 'properties' => array('property' => 'scalar', 'other' => '%$BeanRef') |
||
553 | * 'id' => 'ServiceId', |
||
554 | * 'type' => 'singleton|prototype' |
||
555 | * ) |
||
556 | * |
||
557 | * will create a new object, store it in the service registry, and |
||
558 | * set any relevant properties |
||
559 | * |
||
560 | * Optionally, you can pass a class name directly for creation |
||
561 | * |
||
562 | * To access this from the outside, you should call ->get('Name') to ensure |
||
563 | * the appropriate checks are made on the specific type. |
||
564 | * |
||
565 | * |
||
566 | * @param array $spec |
||
567 | * The specification of the class to instantiate |
||
568 | * @param string $id |
||
569 | * The name of the object being created. If not supplied, then the id will be inferred from the |
||
570 | * object being created |
||
571 | * @param string $type |
||
572 | * Whether to create as a singleton or prototype object. Allows code to be explicit as to how it |
||
573 | * wants the object to be returned |
||
574 | * @return object |
||
575 | */ |
||
576 | protected function instantiate($spec, $id = null, $type = null) |
||
615 | |||
616 | /** |
||
617 | * Inject $object with available objects from the service cache |
||
618 | * |
||
619 | * @todo Track all the existing objects that have had a service bound |
||
620 | * into them, so we can update that binding at a later point if needbe (ie |
||
621 | * if the managed service changes) |
||
622 | * |
||
623 | * @param object $object |
||
624 | * The object to inject |
||
625 | * @param string $asType |
||
626 | * The ID this item was loaded as. This is so that the property configuration |
||
627 | * for a type is referenced correctly in case $object is no longer the same |
||
628 | * type as the loaded config specification had it as. |
||
629 | */ |
||
630 | public function inject($object, $asType = null) |
||
768 | |||
769 | /** |
||
770 | * Helper to set a property's value |
||
771 | * |
||
772 | * @param object $object |
||
773 | * Set an object's property to a specific value |
||
774 | * @param string $name |
||
775 | * The name of the property to set |
||
776 | * @param mixed $value |
||
777 | * The value to set |
||
778 | */ |
||
779 | protected function setObjectProperty($object, $name, $value) |
||
787 | |||
788 | /** |
||
789 | * @deprecated 4.0.0:5.0.0 Use Injector::has() instead |
||
790 | * @param $name |
||
791 | * @return string |
||
792 | */ |
||
793 | public function hasService($name) |
||
799 | |||
800 | /** |
||
801 | * Does the given service exist? |
||
802 | * |
||
803 | * We do a special check here for services that are using compound names. For example, |
||
804 | * we might want to say that a property should be injected with Log.File or Log.Memory, |
||
805 | * but have only registered a 'Log' service, we'll instead return that. |
||
806 | * |
||
807 | * Will recursively call itself for each depth of dotting. |
||
808 | * |
||
809 | * @param string $name |
||
810 | * @return boolean |
||
811 | */ |
||
812 | public function has($name) |
||
816 | |||
817 | /** |
||
818 | * Does the given service exist, and if so, what's the stored name for it? |
||
819 | * |
||
820 | * We do a special check here for services that are using compound names. For example, |
||
821 | * we might want to say that a property should be injected with Log.File or Log.Memory, |
||
822 | * but have only registered a 'Log' service, we'll instead return that. |
||
823 | * |
||
824 | * Will recursively call itself for each depth of dotting. |
||
825 | * |
||
826 | * @param string $name |
||
827 | * @return string|null The name of the service (as it might be different from the one passed in) |
||
828 | */ |
||
829 | public function getServiceName($name) |
||
844 | |||
845 | /** |
||
846 | * Register a service object with an optional name to register it as the |
||
847 | * service for |
||
848 | * |
||
849 | * @param object $service The object to register |
||
850 | * @param string $replace The name of the object to replace (if different to the |
||
851 | * class name of the object to register) |
||
852 | */ |
||
853 | public function registerService($service, $replace = null) |
||
864 | |||
865 | /** |
||
866 | * Removes a named object from the cached list of objects managed |
||
867 | * by the inject |
||
868 | * |
||
869 | * @param string $name The name to unregister |
||
870 | */ |
||
871 | public function unregisterNamedObject($name) |
||
875 | |||
876 | /** |
||
877 | * Clear out all objects that are managed by the injetor. |
||
878 | */ |
||
879 | public function unregisterAllObjects() |
||
883 | |||
884 | /** |
||
885 | * Get a named managed object |
||
886 | * |
||
887 | * Will first check to see if the item has been registered as a configured service/bean |
||
888 | * and return that if so. |
||
889 | * |
||
890 | * Next, will check to see if there's any registered configuration for the given type |
||
891 | * and will then try and load that |
||
892 | * |
||
893 | * Failing all of that, will just return a new instance of the specified object. |
||
894 | * |
||
895 | * @throws NotFoundExceptionInterface No entry was found for **this** identifier. |
||
896 | * |
||
897 | * @param string $name The name of the service to retrieve. If not a registered |
||
898 | * service, then a class of the given name is instantiated |
||
899 | * @param boolean $asSingleton Whether to register the created object as a singleton |
||
900 | * if no other configuration is found |
||
901 | * @param array $constructorArgs Optional set of arguments to pass as constructor arguments |
||
902 | * if this object is to be created from scratch (with $asSingleton = false) |
||
903 | * @return mixed Instance of the specified object |
||
904 | */ |
||
905 | public function get($name, $asSingleton = true, $constructorArgs = null) |
||
915 | |||
916 | /** |
||
917 | * Returns the service, or `null` if it doesnt' exist. See {@link get()} for main usage. |
||
918 | * |
||
919 | * @param string $name |
||
920 | * @param boolean $asSingleton |
||
921 | * @param array $constructorArgs |
||
922 | * @return mixed|null Instance of the specified object (if it exists) |
||
923 | */ |
||
924 | protected function getNamedService($name, $asSingleton = true, $constructorArgs = null) |
||
976 | |||
977 | /** |
||
978 | * Magic method to return an item directly |
||
979 | * |
||
980 | * @param string $name |
||
981 | * The named object to retrieve |
||
982 | * @return mixed |
||
983 | */ |
||
984 | public function __get($name) |
||
988 | |||
989 | /** |
||
990 | * Similar to get() but always returns a new object of the given type |
||
991 | * |
||
992 | * Additional parameters are passed through as |
||
993 | * |
||
994 | * @param string $name |
||
995 | * @param mixed $argument,... arguments to pass to the constructor |
||
996 | * @return mixed A new instance of the specified object |
||
997 | */ |
||
998 | public function create($name, $argument = null) |
||
1004 | |||
1005 | /** |
||
1006 | * Creates an object with the supplied argument array |
||
1007 | * |
||
1008 | * @param string $name Name of the class to create an object of |
||
1009 | * @param array $constructorArgs Arguments to pass to the constructor |
||
1010 | * @return mixed |
||
1011 | */ |
||
1012 | public function createWithArgs($name, $constructorArgs) |
||
1016 | } |
||
1017 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.