Completed
Push — master ( a5dd34...a2187a )
by Lee
10:23
created

PhpDriver   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 82.46%

Importance

Changes 3
Bugs 0 Features 1
Metric Value
wmc 22
lcom 1
cbo 5
dl 0
loc 148
ccs 47
cts 57
cp 0.8246
rs 10
c 3
b 0
f 1

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A create() 0 4 1
B getAllClassNames() 0 25 6
B loadMetadataForClass() 0 33 5
A isDrestResource() 0 8 2
C processMethods() 0 22 7
1
<?php
2
3
namespace Drest\Mapping\Driver;
4
5
use Drest\DrestException;
6
use Drest\Mapping\Annotation;
7
use Drest\Mapping;
8
9
/**
10
 * The PhpDriver reads a configuration file (config.php) rather than utilizing annotations.
11
 */
12
class PhpDriver extends AbstractDriver
13
{
14
    /**
15
     * The classes (resources) from config.php 
16
     * @var array
17
     */
18
    protected $classes = [];
19
20
    /**
21
     * Whether the classes have been read in
22
     * @var bool
23
     */
24
    protected $classesLoaded = false;
25
26
27 20
    public function __construct($paths)
28
    {
29 20
        parent::__construct($paths);
30 20
    }
31
32
    /**
33
     * Factory method for the Annotation Driver
34
     *
35
     * @param  array|string $paths
36
     * @return self
37
     */
38 10
    public static function create($paths = [])
39
    {
40 10
        return new static($paths);
41
    }
42
43
    /**
44
     * Get all the metadata class names known to this driver.
45
     * @return array
46
     * @throws DrestException
47
     * @throws DriverException
48
     */
49 9
    public function getAllClassNames()
50
    {
51 9
        if (empty($this->classes)) {
52 9
            if (empty($this->paths)) {
53
                throw DrestException::pathToConfigFilesRequired();
54
            }
55
56 9
            foreach ($this->paths as $path)
57
            {
58 9
                if(!file_exists($path)) {
59 2
                    throw DriverException::configurationFileDoesntExist($path);
60
                }
61
62 7
                $resources = include $path;
63
64 7
                if(!is_array($resources)) {
65 1
                    throw DriverException::configurationFileIsInvalid('Php');
66
                }
67
68 6
                $this->classes = array_merge($this->classes, $resources);
69 6
            }
70 6
        }
71
72 6
        return array_keys($this->classes);
73
    }
74
75
    /**
76
     * Load metadata for a class name
77
     * @param  object|string                $className - Pass in either the class name, or an instance of that class
78
     * @return Mapping\ClassMetaData|null   $metaData - return null if metadata couldn't be populated from annotations
79
     * @throws DrestException
80
     */
81 5
    public function loadMetadataForClass($className)
82
    {
83 5
        if (!$this->classesLoaded)
84 5
        {
85 5
            $this->getAllClassNames();
86 5
            $this->classesLoaded = true;
87 5
        }
88
89 5
        $class = new \ReflectionClass($className);
90
91 5
        $metadata = new Mapping\ClassMetaData($class);
92
93 5
        if(!isset($this->classes[$className])) {
94 1
            return null;
95
        }
96
97 4
        $resource = $this->classes[$className];
98
99 4
        if ($resource['routes'] === null) {
100
            throw DrestException::annotatedResourceRequiresAtLeastOneServiceDefinition($resource['name']);
101
        }
102
103 4
        if (is_array($resource['representations']))
104 4
        {
105 4
            $metadata->addRepresentations($resource['representations']);
106 4
        }
107
108 4
        $this->processRoutes($resource['routes'], $metadata);
109
110 1
        $this->processMethods($resource, $metadata);
111
        
112 1
        return $metadata;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $metadata; (Drest\Mapping\ClassMetaData) is incompatible with the return type declared by the abstract method Drest\Mapping\Driver\Abs...r::loadMetadataForClass of type Drest\Mapping\Driver\ClassMetadata.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
113
    }
114
115
    /**
116
     * Does the class contain a drest resource object
117
     *
118
     * @param  string $className
119
     * @return bool
120
     */
121
    public function isDrestResource($className)
122
    {
123
        if(!in_array($className, array_keys($this->classes)))
124
        {
125
            return false;
126
        }
127
        return true;
128
    }
129
130
131
    /**
132
     * Process the method
133
     * @param $resource
134
     * @param Mapping\ClassMetaData $metadata
135
     * @throws DrestException
136
     */
137 1
    protected function processMethods($resource, Mapping\ClassMetaData $metadata)
138
    {
139
        /* @var \ReflectionMethod $method */
140 1
        foreach ($resource['routes'] as $route) {
141
            // Make sure the for is not empty
142 1
            if (!isset($route['name']) || !is_string($route['name'])) {
143
                throw DrestException::handleForCannotBeEmpty();
144
            }
145 1
            if (($routeMetaData = $metadata->getRouteMetaData($route['name'])) === false) {
146
                throw DrestException::handleAnnotationDoesntMatchRouteName($route['name']);
147
            }
148 1
            if ($routeMetaData->hasHandleCall()) {
149
                // There is already a handle set for this route
150
                throw DrestException::handleAlreadyDefinedForRoute($routeMetaData);
151
            }
152
153
            // Set the handle
154 1
            if (isset($route['handle_call'])) {
155 1
                $routeMetaData->setHandleCall($route['handle_call']);
156 1
            }
157 1
        }
158
    }
159
}