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 |
||
| 130 | class Injector implements ContainerInterface |
||
| 131 | { |
||
| 132 | |||
| 133 | /** |
||
| 134 | * Local store of all services |
||
| 135 | * |
||
| 136 | * @var array |
||
| 137 | */ |
||
| 138 | private $serviceCache; |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Cache of items that need to be mapped for each service that gets injected |
||
| 142 | * |
||
| 143 | * @var array |
||
| 144 | */ |
||
| 145 | private $injectMap; |
||
| 146 | |||
| 147 | /** |
||
| 148 | * A store of all the service configurations that have been defined. |
||
| 149 | * |
||
| 150 | * @var array |
||
| 151 | */ |
||
| 152 | private $specs; |
||
| 153 | |||
| 154 | /** |
||
| 155 | * A map of all the properties that should be automagically set on all |
||
| 156 | * objects instantiated by the injector |
||
| 157 | */ |
||
| 158 | private $autoProperties; |
||
| 159 | |||
| 160 | /** |
||
| 161 | * A singleton if you want to use it that way |
||
| 162 | * |
||
| 163 | * @var Injector |
||
| 164 | */ |
||
| 165 | private static $instance; |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Indicates whether or not to automatically scan properties in injected objects to auto inject |
||
| 169 | * stuff, similar to the way grails does things. |
||
| 170 | * |
||
| 171 | * @var boolean |
||
| 172 | */ |
||
| 173 | private $autoScanProperties = false; |
||
| 174 | |||
| 175 | /** |
||
| 176 | * The default factory used to create new instances. |
||
| 177 | * |
||
| 178 | * The {@link InjectionCreator} is used by default, which simply directly |
||
| 179 | * creates objects. This can be changed to use a different default creation |
||
| 180 | * method if desired. |
||
| 181 | * |
||
| 182 | * Each individual component can also specify a custom factory to use by |
||
| 183 | * using the `factory` parameter. |
||
| 184 | * |
||
| 185 | * @var Factory |
||
| 186 | */ |
||
| 187 | protected $objectCreator; |
||
| 188 | |||
| 189 | /** |
||
| 190 | * Locator for determining Config properties for services |
||
| 191 | * |
||
| 192 | * @var ServiceConfigurationLocator |
||
| 193 | */ |
||
| 194 | protected $configLocator; |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Create a new injector. |
||
| 198 | * |
||
| 199 | * @param array $config |
||
| 200 | * Service configuration |
||
| 201 | */ |
||
| 202 | public function __construct($config = null) |
||
| 226 | |||
| 227 | /** |
||
| 228 | * The injector instance this one was copied from when Injector::nest() was called. |
||
| 229 | * |
||
| 230 | * @var Injector |
||
| 231 | */ |
||
| 232 | protected $nestedFrom = null; |
||
| 233 | |||
| 234 | /** |
||
| 235 | * @return Injector |
||
| 236 | */ |
||
| 237 | public static function inst() |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Make the newly active {@link Injector} be a copy of the current active |
||
| 244 | * {@link Injector} instance. |
||
| 245 | * |
||
| 246 | * You can then make changes to the injector with methods such as |
||
| 247 | * {@link Injector::inst()->registerService()} which will be discarded |
||
| 248 | * upon a subsequent call to {@link Injector::unnest()} |
||
| 249 | * |
||
| 250 | * @return Injector Reference to new active Injector instance |
||
| 251 | */ |
||
| 252 | public static function nest() |
||
| 259 | |||
| 260 | /** |
||
| 261 | * Change the active Injector back to the Injector instance the current active |
||
| 262 | * Injector object was copied from. |
||
| 263 | * |
||
| 264 | * @return Injector Reference to restored active Injector instance |
||
| 265 | */ |
||
| 266 | View Code Duplication | public static function unnest() |
|
| 280 | |||
| 281 | /** |
||
| 282 | * Indicate whether we auto scan injected objects for properties to set. |
||
| 283 | * |
||
| 284 | * @param boolean $val |
||
| 285 | */ |
||
| 286 | public function setAutoScanProperties($val) |
||
| 290 | |||
| 291 | /** |
||
| 292 | * Sets the default factory to use for creating new objects. |
||
| 293 | * |
||
| 294 | * @param \SilverStripe\Core\Injector\Factory $obj |
||
| 295 | */ |
||
| 296 | public function setObjectCreator(Factory $obj) |
||
| 300 | |||
| 301 | /** |
||
| 302 | * @return Factory |
||
| 303 | */ |
||
| 304 | public function getObjectCreator() |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Set the configuration locator |
||
| 311 | * @param ServiceConfigurationLocator $configLocator |
||
| 312 | */ |
||
| 313 | public function setConfigLocator($configLocator) |
||
| 317 | |||
| 318 | /** |
||
| 319 | * Retrieve the configuration locator |
||
| 320 | * @return ServiceConfigurationLocator |
||
| 321 | */ |
||
| 322 | public function getConfigLocator() |
||
| 326 | |||
| 327 | /** |
||
| 328 | * Add in a specific mapping that should be catered for on a type. |
||
| 329 | * This allows configuration of what should occur when an object |
||
| 330 | * of a particular type is injected, and what items should be injected |
||
| 331 | * for those properties / methods. |
||
| 332 | * |
||
| 333 | * @param string $class The class to set a mapping for |
||
| 334 | * @param string $property The property to set the mapping for |
||
| 335 | * @param string $toInject The registered type that will be injected |
||
| 336 | * @param string $injectVia Whether to inject by setting a property or calling a setter |
||
| 337 | */ |
||
| 338 | public function setInjectMapping($class, $property, $toInject, $injectVia = 'property') |
||
| 346 | |||
| 347 | /** |
||
| 348 | * Add an object that should be automatically set on managed objects |
||
| 349 | * |
||
| 350 | * This allows you to specify, for example, that EVERY managed object |
||
| 351 | * will be automatically inject with a log object by the following |
||
| 352 | * |
||
| 353 | * $injector->addAutoProperty('log', new Logger()); |
||
| 354 | * |
||
| 355 | * @param string $property |
||
| 356 | * the name of the property |
||
| 357 | * @param object $object |
||
| 358 | * the object to be set |
||
| 359 | * @return $this |
||
| 360 | */ |
||
| 361 | public function addAutoProperty($property, $object) |
||
| 366 | |||
| 367 | /** |
||
| 368 | * Load services using the passed in configuration for those services |
||
| 369 | * |
||
| 370 | * @param array $config |
||
| 371 | * @return $this |
||
| 372 | */ |
||
| 373 | public function load($config = array()) |
||
| 439 | |||
| 440 | /** |
||
| 441 | * Update the configuration of an already defined service |
||
| 442 | * |
||
| 443 | * Use this if you don't want to register a complete new config, just append |
||
| 444 | * to an existing configuration. Helpful to avoid overwriting someone else's changes |
||
| 445 | * |
||
| 446 | * updateSpec('RequestProcessor', 'filters', '%$MyFilter') |
||
| 447 | * |
||
| 448 | * @param string $id |
||
| 449 | * The name of the service to update the definition for |
||
| 450 | * @param string $property |
||
| 451 | * The name of the property to update. |
||
| 452 | * @param mixed $value |
||
| 453 | * The value to set |
||
| 454 | * @param boolean $append |
||
| 455 | * Whether to append (the default) when the property is an array |
||
| 456 | */ |
||
| 457 | public function updateSpec($id, $property, $value, $append = true) |
||
| 475 | |||
| 476 | /** |
||
| 477 | * Update a class specification to convert constructor configuration information if needed |
||
| 478 | * |
||
| 479 | * We do this as a separate process to avoid unneeded calls to convertServiceProperty |
||
| 480 | * |
||
| 481 | * @param array $spec |
||
| 482 | * The class specification to update |
||
| 483 | */ |
||
| 484 | protected function updateSpecConstructor(&$spec) |
||
| 490 | |||
| 491 | /** |
||
| 492 | * Recursively convert a value into its proper representation with service references |
||
| 493 | * resolved to actual objects |
||
| 494 | * |
||
| 495 | * @param string $value |
||
| 496 | * @return array|mixed|string |
||
| 497 | */ |
||
| 498 | public function convertServiceProperty($value) |
||
| 527 | |||
| 528 | /** |
||
| 529 | * Instantiate an object from the given spec, without saving to the service cache |
||
| 530 | */ |
||
| 531 | public function createFromSpec(array $spec) |
||
| 536 | |||
| 537 | /** |
||
| 538 | * Instantiate a managed object |
||
| 539 | * |
||
| 540 | * Given a specification of the form |
||
| 541 | * |
||
| 542 | * array( |
||
| 543 | * 'class' => 'ClassName', |
||
| 544 | * 'properties' => array('property' => 'scalar', 'other' => '%$BeanRef') |
||
| 545 | * 'id' => 'ServiceId', |
||
| 546 | * 'type' => 'singleton|prototype' |
||
| 547 | * ) |
||
| 548 | * |
||
| 549 | * will create a new object, store it in the service registry, and |
||
| 550 | * set any relevant properties |
||
| 551 | * |
||
| 552 | * Optionally, you can pass a class name directly for creation |
||
| 553 | * |
||
| 554 | * To access this from the outside, you should call ->get('Name') to ensure |
||
| 555 | * the appropriate checks are made on the specific type. |
||
| 556 | * |
||
| 557 | * |
||
| 558 | * @param array $spec |
||
| 559 | * The specification of the class to instantiate |
||
| 560 | * @param string $id |
||
| 561 | * The name of the object being created. If not supplied, then the id will be inferred from the |
||
| 562 | * object being created |
||
| 563 | * @param string $type |
||
| 564 | * Whether to create as a singleton or prototype object. Allows code to be explicit as to how it |
||
| 565 | * wants the object to be returned |
||
| 566 | * @return object |
||
| 567 | */ |
||
| 568 | protected function instantiate($spec, $id = null, $type = null) |
||
| 607 | |||
| 608 | /** |
||
| 609 | * Inject $object with available objects from the service cache |
||
| 610 | * |
||
| 611 | * @todo Track all the existing objects that have had a service bound |
||
| 612 | * into them, so we can update that binding at a later point if needbe (ie |
||
| 613 | * if the managed service changes) |
||
| 614 | * |
||
| 615 | * @param object $object |
||
| 616 | * The object to inject |
||
| 617 | * @param string $asType |
||
| 618 | * The ID this item was loaded as. This is so that the property configuration |
||
| 619 | * for a type is referenced correctly in case $object is no longer the same |
||
| 620 | * type as the loaded config specification had it as. |
||
| 621 | */ |
||
| 622 | protected function inject($object, $spec, $asType = null) |
||
| 758 | |||
| 759 | /** |
||
| 760 | * Helper to set a property's value |
||
| 761 | * |
||
| 762 | * @param object $object |
||
| 763 | * Set an object's property to a specific value |
||
| 764 | * @param string $name |
||
| 765 | * The name of the property to set |
||
| 766 | * @param mixed $value |
||
| 767 | * The value to set |
||
| 768 | */ |
||
| 769 | protected function setObjectProperty($object, $name, $value) |
||
| 777 | |||
| 778 | /** |
||
| 779 | * @deprecated 4.0.0:5.0.0 Use Injector::has() instead |
||
| 780 | * @param $name |
||
| 781 | * @return string |
||
| 782 | */ |
||
| 783 | public function hasService($name) |
||
| 789 | |||
| 790 | /** |
||
| 791 | * Does the given service exist? |
||
| 792 | * |
||
| 793 | * We do a special check here for services that are using compound names. For example, |
||
| 794 | * we might want to say that a property should be injected with Log.File or Log.Memory, |
||
| 795 | * but have only registered a 'Log' service, we'll instead return that. |
||
| 796 | * |
||
| 797 | * Will recursively call itself for each depth of dotting. |
||
| 798 | * |
||
| 799 | * @param string $name |
||
| 800 | * @return boolean |
||
| 801 | */ |
||
| 802 | public function has($name) |
||
| 806 | |||
| 807 | /** |
||
| 808 | * Does the given service exist, and if so, what's the stored name for it? |
||
| 809 | * |
||
| 810 | * We do a special check here for services that are using compound names. For example, |
||
| 811 | * we might want to say that a property should be injected with Log.File or Log.Memory, |
||
| 812 | * but have only registered a 'Log' service, we'll instead return that. |
||
| 813 | * |
||
| 814 | * Will recursively call itself for each depth of dotting. |
||
| 815 | * |
||
| 816 | * @param string $name |
||
| 817 | * @return string|null The name of the service (as it might be different from the one passed in) |
||
| 818 | */ |
||
| 819 | public function getServiceName($name) |
||
| 834 | |||
| 835 | /** |
||
| 836 | * Register a service object with an optional name to register it as the |
||
| 837 | * service for |
||
| 838 | * |
||
| 839 | * @param object $service The object to register |
||
| 840 | * @param string $replace The name of the object to replace (if different to the |
||
| 841 | * class name of the object to register) |
||
| 842 | * @return $this |
||
| 843 | */ |
||
| 844 | public function registerService($service, $replace = null) |
||
| 855 | |||
| 856 | /** |
||
| 857 | * Removes a named object from the cached list of objects managed |
||
| 858 | * by the inject |
||
| 859 | * |
||
| 860 | * @param string $name The name to unregister |
||
| 861 | * @return $this |
||
| 862 | */ |
||
| 863 | public function unregisterNamedObject($name) |
||
| 868 | |||
| 869 | /** |
||
| 870 | * Clear out objects of one or more types that are managed by the injetor. |
||
| 871 | * |
||
| 872 | * @param array|string $types Base class of object (not service name) to remove |
||
| 873 | * @return $this |
||
| 874 | */ |
||
| 875 | public function unregisterObjects($types) |
||
| 896 | |||
| 897 | /** |
||
| 898 | * Get a named managed object |
||
| 899 | * |
||
| 900 | * Will first check to see if the item has been registered as a configured service/bean |
||
| 901 | * and return that if so. |
||
| 902 | * |
||
| 903 | * Next, will check to see if there's any registered configuration for the given type |
||
| 904 | * and will then try and load that |
||
| 905 | * |
||
| 906 | * Failing all of that, will just return a new instance of the specified object. |
||
| 907 | * |
||
| 908 | * @throws NotFoundExceptionInterface No entry was found for **this** identifier. |
||
| 909 | * |
||
| 910 | * @param string $name The name of the service to retrieve. If not a registered |
||
| 911 | * service, then a class of the given name is instantiated |
||
| 912 | * @param boolean $asSingleton Whether to register the created object as a singleton |
||
| 913 | * if no other configuration is found |
||
| 914 | * @param array $constructorArgs Optional set of arguments to pass as constructor arguments |
||
| 915 | * if this object is to be created from scratch (with $asSingleton = false) |
||
| 916 | * @return mixed Instance of the specified object |
||
| 917 | */ |
||
| 918 | public function get($name, $asSingleton = true, $constructorArgs = []) |
||
| 928 | |||
| 929 | /** |
||
| 930 | * Returns the service, or `null` if it doesnt' exist. See {@link get()} for main usage. |
||
| 931 | * |
||
| 932 | * @param string $name |
||
| 933 | * @param boolean $asSingleton |
||
| 934 | * @param array $constructorArgs |
||
| 935 | * @return mixed|null Instance of the specified object (if it exists) |
||
| 936 | */ |
||
| 937 | protected function getNamedService($name, $asSingleton = true, $constructorArgs = []) |
||
| 997 | |||
| 998 | /** |
||
| 999 | * Detect service references with constructor arguments included. |
||
| 1000 | * These will be split out of the service name reference and appended |
||
| 1001 | * to the $args |
||
| 1002 | * |
||
| 1003 | * @param string $name |
||
| 1004 | * @param array $args |
||
| 1005 | * @return array Two items with name and new args |
||
| 1006 | */ |
||
| 1007 | protected function normaliseArguments($name, $args = []) |
||
| 1019 | |||
| 1020 | /** |
||
| 1021 | * Magic method to return an item directly |
||
| 1022 | * |
||
| 1023 | * @param string $name |
||
| 1024 | * The named object to retrieve |
||
| 1025 | * @return mixed |
||
| 1026 | */ |
||
| 1027 | public function __get($name) |
||
| 1031 | |||
| 1032 | /** |
||
| 1033 | * Similar to get() but always returns a new object of the given type |
||
| 1034 | * |
||
| 1035 | * Additional parameters are passed through as |
||
| 1036 | * |
||
| 1037 | * @param string $name |
||
| 1038 | * @param mixed $argument,... arguments to pass to the constructor |
||
| 1039 | * @return mixed A new instance of the specified object |
||
| 1040 | */ |
||
| 1041 | public function create($name, $argument = null) |
||
| 1047 | |||
| 1048 | /** |
||
| 1049 | * Creates an object with the supplied argument array |
||
| 1050 | * |
||
| 1051 | * @param string $name Name of the class to create an object of |
||
| 1052 | * @param array $constructorArgs Arguments to pass to the constructor |
||
| 1053 | * @return mixed |
||
| 1054 | */ |
||
| 1055 | public function createWithArgs($name, $constructorArgs) |
||
| 1059 | } |
||
| 1060 |
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.