| Total Complexity | 60 |
| Total Lines | 348 |
| Duplicated Lines | 4.31 % |
| Coverage | 4.41% |
| Changes | 0 | ||
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 MetadataProvider 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 MetadataProvider, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 24 | class MetadataProvider extends MetadataBaseProvider |
||
| 25 | 1 | { |
|
| 26 | protected $multConstraints = ['0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']]; |
||
| 27 | protected static $metaNAMESPACE = 'Data'; |
||
| 28 | 1 | protected static $isBooted = false; |
|
| 29 | 1 | const POLYMORPHIC = 'polyMorphicPlaceholder'; |
|
| 30 | const POLYMORPHIC_PLURAL = 'polyMorphicPlaceholders'; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * @var Map The completed object map set at post Implement; |
||
| 34 | */ |
||
| 35 | private $completedObjectMap; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @return \AlgoWeb\PODataLaravel\Models\ObjectMap\Map |
||
| 39 | */ |
||
| 40 | public function getObjectMap() |
||
| 41 | { |
||
| 42 | return $this->completedObjectMap; |
||
| 43 | } |
||
| 44 | |||
| 45 | protected static $afterExtract; |
||
| 46 | protected static $afterUnify; |
||
| 47 | protected static $afterVerify; |
||
| 48 | protected static $afterImplement; |
||
| 49 | |||
| 50 | public static function setAfterExtract(callable $method) |
||
| 51 | { |
||
| 52 | self::$afterExtract = $method; |
||
| 53 | } |
||
| 54 | |||
| 55 | public static function setAfterUnify(callable $method) |
||
| 56 | { |
||
| 57 | self::$afterUnify = $method; |
||
| 58 | } |
||
| 59 | |||
| 60 | public static function setAfterVerify(callable $method) |
||
| 61 | { |
||
| 62 | self::$afterVerify = $method; |
||
| 63 | } |
||
| 64 | |||
| 65 | public static function setAfterImplement(callable $method) |
||
| 68 | } |
||
| 69 | |||
| 70 | |||
| 71 | protected $relationHolder; |
||
| 72 | |||
| 73 | public function __construct($app) |
||
| 74 | { |
||
| 75 | parent::__construct($app); |
||
| 76 | $this->relationHolder = new MetadataGubbinsHolder(); |
||
| 77 | self::$isBooted = false; |
||
| 78 | } |
||
| 79 | |||
| 80 | private function extract(array $modelNames) |
||
| 81 | { |
||
| 82 | $objectMap = App::make('objectmap'); |
||
| 83 | foreach ($modelNames as $modelName) { |
||
| 84 | try { |
||
| 85 | $modelInstance = App::make($modelName); |
||
| 86 | } catch (BindingResolutionException $e) { |
||
| 87 | // if we can't instantiate modelName for whatever reason, move on |
||
| 88 | continue; |
||
| 89 | } |
||
| 90 | $gubbins = $modelInstance->extractGubbins(); |
||
| 91 | $isEmpty = 0 === count($gubbins->getFields()); |
||
| 92 | $inArtisan = $this->isRunningInArtisan(); |
||
| 93 | if (!($isEmpty && $inArtisan)) { |
||
| 94 | $objectMap->addEntity($gubbins); |
||
| 95 | } |
||
| 96 | } |
||
| 97 | if (null != self::$afterExtract) { |
||
| 98 | $func = self::$afterExtract; |
||
| 99 | $func($objectMap); |
||
| 100 | } |
||
| 101 | return $objectMap; |
||
| 102 | } |
||
| 103 | |||
| 104 | private function unify(Map $objectMap) |
||
| 116 | } |
||
| 117 | |||
| 118 | private function verify(Map $objectModel) |
||
| 119 | { |
||
| 120 | $objectModel->isOK(); |
||
| 121 | if (null != self::$afterVerify) { |
||
| 122 | $func = self::$afterVerify; |
||
| 123 | $func($objectModel); |
||
| 124 | } |
||
| 125 | } |
||
| 126 | |||
| 127 | private function implement(Map $objectModel) |
||
| 128 | { |
||
| 129 | $meta = App::make('metadata'); |
||
| 130 | $entities = $objectModel->getEntities(); |
||
| 131 | foreach ($entities as $entity) { |
||
| 132 | $baseType = null; |
||
| 133 | $className = $entity->getClassName(); |
||
| 134 | $entityName = $entity->getName(); |
||
| 135 | $entityType = $meta->addEntityType(new \ReflectionClass($className), $entityName, false, $baseType); |
||
| 136 | assert($entityType->hasBaseType() === isset($baseType)); |
||
|
|
|||
| 137 | $entity->setOdataResourceType($entityType); |
||
| 138 | $this->implementProperties($entity); |
||
| 139 | $meta->addResourceSet($entity->getClassName(), $entityType); |
||
| 140 | $meta->oDataEntityMap[$className] = $meta->oDataEntityMap[$entityName]; |
||
| 141 | } |
||
| 142 | $metaCount = count($meta->oDataEntityMap); |
||
| 143 | $entityCount = count($entities); |
||
| 144 | $expected = 2 * $entityCount; |
||
| 145 | assert($metaCount == $expected, 'Expected ' . $expected . ' items, actually got '.$metaCount); |
||
| 146 | |||
| 147 | if (0 === count($objectModel->getAssociations())) { |
||
| 148 | return; |
||
| 149 | } |
||
| 150 | $assoc = $objectModel->getAssociations(); |
||
| 151 | $assoc = null === $assoc ? [] : $assoc; |
||
| 152 | foreach ($assoc as $association) { |
||
| 153 | assert($association->isOk()); |
||
| 154 | $this->implementAssociationsMonomorphic($objectModel, $association); |
||
| 155 | } |
||
| 156 | if (null != self::$afterImplement) { |
||
| 157 | $func = self::$afterImplement; |
||
| 158 | $func($objectModel); |
||
| 159 | } |
||
| 160 | } |
||
| 161 | |||
| 162 | private function implementAssociationsMonomorphic(Map $objectModel, AssociationMonomorphic $associationUnderHammer) |
||
| 200 | ); |
||
| 201 | } |
||
| 202 | } |
||
| 203 | |||
| 204 | private function implementProperties(EntityGubbins $unifiedEntity) |
||
| 205 | { |
||
| 206 | $meta = App::make('metadata'); |
||
| 207 | $odataEntity = $unifiedEntity->getOdataResourceType(); |
||
| 208 | $keyFields = $unifiedEntity->getKeyFields(); |
||
| 209 | $fields = $unifiedEntity->getFields(); |
||
| 210 | foreach ($keyFields as $keyField) { |
||
| 211 | $meta->addKeyProperty($odataEntity, $keyField->getName(), $keyField->getEdmFieldType()); |
||
| 212 | } |
||
| 213 | |||
| 214 | foreach ($fields as $field) { |
||
| 215 | if (in_array($field, $keyFields)) { |
||
| 216 | continue; |
||
| 217 | } |
||
| 218 | if ($field->getPrimitiveType() == 'blob') { |
||
| 219 | $odataEntity->setMediaLinkEntry(true); |
||
| 220 | $streamInfo = new ResourceStreamInfo($field->getName()); |
||
| 221 | assert($odataEntity->isMediaLinkEntry()); |
||
| 222 | $odataEntity->addNamedStream($streamInfo); |
||
| 223 | continue; |
||
| 224 | } |
||
| 225 | |||
| 226 | $default = $field->getDefaultValue(); |
||
| 227 | $isFieldBool = TypeCode::BOOLEAN == $field->getEdmFieldType(); |
||
| 228 | $default = $isFieldBool ? ($default ? 'true' : 'false') : strval($default); |
||
| 229 | |||
| 230 | $meta->addPrimitiveProperty( |
||
| 231 | $odataEntity, |
||
| 232 | $field->getName(), |
||
| 233 | $field->getEdmFieldType(), |
||
| 234 | $field->getFieldType() == EntityFieldType::PRIMITIVE_BAG(), |
||
| 235 | $default, |
||
| 236 | $field->getIsNullable() |
||
| 237 | ); |
||
| 238 | } |
||
| 239 | } |
||
| 240 | |||
| 241 | /** |
||
| 242 | * Bootstrap the application services. Post-boot. |
||
| 243 | * |
||
| 244 | * @param mixed $reset |
||
| 245 | * |
||
| 246 | * @return void |
||
| 247 | */ |
||
| 248 | public function boot($reset = true) |
||
| 249 | { |
||
| 250 | self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data'); |
||
| 251 | // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early |
||
| 252 | try { |
||
| 253 | if (!Schema::hasTable(config('database.migrations'))) { |
||
| 254 | return; |
||
| 255 | } |
||
| 256 | } catch (\Exception $e) { |
||
| 257 | return; |
||
| 258 | } |
||
| 259 | |||
| 260 | assert(false === self::$isBooted, 'Provider booted twice'); |
||
| 261 | $isCaching = true === $this->getIsCaching(); |
||
| 262 | $meta = Cache::get('metadata'); |
||
| 263 | $objectMap = Cache::get('objectmap'); |
||
| 264 | $hasCache = null != $meta && null != $objectMap; |
||
| 265 | |||
| 266 | if ($isCaching && $hasCache) { |
||
| 267 | App::instance('metadata', $meta); |
||
| 268 | App::instance('objectmap', $objectMap); |
||
| 269 | return; |
||
| 270 | } |
||
| 271 | $meta = App::make('metadata'); |
||
| 272 | if (false !== $reset) { |
||
| 273 | $this->reset(); |
||
| 274 | } |
||
| 275 | |||
| 276 | $modelNames = $this->getCandidateModels(); |
||
| 277 | $objectModel = $this->extract($modelNames); |
||
| 278 | $objectModel = $this->unify($objectModel); |
||
| 279 | $this->verify($objectModel); |
||
| 280 | $this->implement($objectModel); |
||
| 281 | $this->completedObjectMap = $objectModel; |
||
| 282 | $key = 'metadata'; |
||
| 283 | $objKey = 'objectmap'; |
||
| 284 | $this->handlePostBoot($isCaching, $hasCache, $key, $meta); |
||
| 285 | $this->handlePostBoot($isCaching, $hasCache, $objKey, $objectModel); |
||
| 286 | self::$isBooted = true; |
||
| 287 | } |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Register the application services. Boot-time only. |
||
| 291 | * |
||
| 292 | * @return void |
||
| 293 | */ |
||
| 294 | public function register() |
||
| 295 | { |
||
| 296 | $this->app->singleton('metadata', function ($app) { |
||
| 297 | return new SimpleMetadataProvider('Data', self::$metaNAMESPACE); |
||
| 298 | }); |
||
| 299 | $this->app->singleton('objectmap', function ($app) { |
||
| 300 | return new Map(); |
||
| 301 | }); |
||
| 302 | } |
||
| 303 | |||
| 304 | /** |
||
| 305 | * @return array |
||
| 306 | */ |
||
| 307 | protected function getCandidateModels() |
||
| 308 | { |
||
| 309 | $classes = $this->getClassMap(); |
||
| 310 | $ends = []; |
||
| 311 | $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App'; |
||
| 312 | foreach ($classes as $name) { |
||
| 313 | if (\Illuminate\Support\Str::startsWith($name, $startName)) { |
||
| 314 | if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name)) && |
||
| 315 | is_subclass_of($name, '\\Illuminate\\Database\\Eloquent\\Model')) { |
||
| 316 | $ends[] = $name; |
||
| 317 | } |
||
| 318 | } |
||
| 319 | } |
||
| 320 | return $ends; |
||
| 321 | } |
||
| 322 | |||
| 323 | /** |
||
| 324 | * @return MetadataGubbinsHolder |
||
| 325 | */ |
||
| 326 | public function getRelationHolder() |
||
| 329 | } |
||
| 330 | |||
| 331 | public function reset() |
||
| 332 | { |
||
| 333 | self::$isBooted = false; |
||
| 334 | self::$afterExtract = null; |
||
| 335 | self::$afterUnify = null; |
||
| 336 | self::$afterVerify = null; |
||
| 337 | self::$afterImplement = null; |
||
| 338 | } |
||
| 339 | |||
| 340 | /** |
||
| 341 | * Resolve possible reverse relation property names. |
||
| 342 | * |
||
| 343 | * @param Model $source |
||
| 344 | * @param $propName |
||
| 345 | * @return null|string |
||
| 346 | * @internal param Model $target |
||
| 347 | */ |
||
| 348 | public function resolveReverseProperty(Model $source, $propName) |
||
| 349 | { |
||
| 350 | assert(is_string($propName), 'Property name must be string'); |
||
| 351 | $entity = $this->getObjectMap()->resolveEntity(get_class($source)); |
||
| 352 | if (null === $entity) { |
||
| 353 | $msg = 'Source model not defined'; |
||
| 354 | throw new \InvalidArgumentException($msg); |
||
| 355 | } |
||
| 356 | $association = $entity->resolveAssociation($propName); |
||
| 357 | if (null === $association) { |
||
| 358 | return null; |
||
| 359 | } |
||
| 360 | $isFirst = $propName === $association->getFirst()->getRelationName(); |
||
| 361 | if (!$isFirst) { |
||
| 362 | return $association->getFirst()->getRelationName(); |
||
| 363 | } |
||
| 364 | |||
| 365 | assert($association instanceof AssociationMonomorphic); |
||
| 366 | return $association->getLast()->getRelationName(); |
||
| 367 | } |
||
| 368 | |||
| 369 | public function isRunningInArtisan() |
||
| 372 | } |
||
| 373 | } |
||
| 374 |
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.