Completed
Push — master ( 45c651...8bfbde )
by Damien
02:27
created

LayerChain::processInboundLayers()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 30
Code Lines 14

Duplication

Lines 30
Ratio 100 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 30
loc 30
rs 8.439
cc 6
eloc 14
nc 6
nop 1
1
<?php
2
/**
3
 * Veto.
4
 * PHP Microframework.
5
 *
6
 * @author Damien Walsh <[email protected]>
7
 * @copyright Damien Walsh 2013-2014
8
 * @version 0.1
9
 * @package veto
10
 */
11
namespace Veto\Layer;
12
13
use Veto\DI\AbstractContainerAccessor;
14
use Veto\HTTP\Request;
15
use Veto\HTTP\Response;
16
17
/**
18
 * LayerChain
19
 *
20
 * Groups layers together to form a Request->Response flow
21
 */
22
class LayerChain extends AbstractContainerAccessor
23
{
24
    /**
25
     * An associative, 2D array of layers that are configured.
26
     * Layers are stored here keyed by priority then name.
27
     *
28
     * @var array
29
     */
30
    private $layers = array();
31
32
    /**
33
     * Add a layer to this LayerChain.
34
     *
35
     * @param object $layer
36
     * @param int $priority
37
     */
38
    public function addLayer($layer, $priority = 0)
39
    {
40
        // Enforce the type of $layer
41
        if (!($layer instanceof InboundLayerInterface || $layer instanceof OutboundLayerInterface)) {
42
            throw new \InvalidArgumentException(
43
                'Argument 1 of '  . __CLASS__ . '::' . __METHOD__ .
44
                ' must be either an InboundLayerInterface or an OutboundLayerInterface instance.'
45
            );
46
        }
47
48
        $this->layers[$priority][] = $layer;
49
50
        // TODO: Improve the efficiency of this by _not_ sorting after every insertion
51
        ksort($this->layers);
52
    }
53
54
    /**
55
     * @param Request $request
56
     * @return Response
57
     * @throws \RuntimeException
58
     */
59
    public function processLayers(Request $request)
60
    {
61
        // There must be at least one inbound layer, otherwise our Request will never become a Response
62
        if (0 === count($this->layers)) {
63
            throw new \RuntimeException(
64
                'At least one layer must be defined in order to handle requests.'
65
            );
66
        }
67
68
        $response = $this->processInboundLayers($request);
69
70
        // By the end of the inbound layer list, a response should have been obtained
71
        if (!$response instanceof Response) {
72
            throw new \RuntimeException(
73
                'At least one inbound layer must produce a Response instance. ' .
74
                'The final processed layer returned a "' . gettype($response) . '".'
75
            );
76
        }
77
78
        // The response should now be processed by the outbound layers
79
        return $this->processOutboundLayers($response);
80
    }
81
82
    /**
83
     * @param Request $request
84
     * @return Request
85
     */
86 View Code Duplication
    private function processInboundLayers(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
    {
88
        $result = $request;
89
90
        // Pass through layers inwards
91
        foreach ($this->layers as $priority => $layers) {
92
            foreach ($layers as $layerName => $layer) {
93
94
                if (!($layer instanceof InboundLayerInterface)) {
95
                    continue;
96
                }
97
98
                $result = $layer->in($result);
99
100
                // If the layer produces a response, no more inbound layers may execute
101
                if ($result instanceof Response) {
102
                    return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (Veto\HTTP\Response) is incompatible with the return type documented by Veto\Layer\LayerChain::processInboundLayers of type Veto\HTTP\Request.

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...
103
                }
104
105
                if (!$result instanceof Request) {
106
                    throw new \RuntimeException(
107
                        'Each inbound layer of the chain must produce a Request or Response type. ' .
108
                        'The "' . $layerName . '" layer returned ' . gettype($request) . '.'
109
                    );
110
                }
111
            }
112
        }
113
114
        return $result;
115
    }
116
117
    /**
118
     * @param Response $response
119
     * @return Response
120
     */
121 View Code Duplication
    private function processOutboundLayers(Response $response)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
122
    {
123
        $result = $response;
124
125
        foreach ($this->layers as $priority => $layers) {
126
            foreach ($layers as $layerName => $layer) {
127
128
                if (!($layer instanceof OutboundLayerInterface)) {
129
                    continue;
130
                }
131
132
                $result = $layer->out($result);
133
134
                if (!$result instanceof Response) {
135
                    throw new \RuntimeException(
136
                        'Each outbound layer of the chain must produce a Response type. ' .
137
                        'The "' . $layerName . '" layer returned ' . gettype($response) . '.'
138
                    );
139
                }
140
            }
141
        }
142
143
        return $result;
144
    }
145
146
    /**
147
     * Get the layers currently added to this LayerChain, keyed by priority group.
148
     *
149
     * @return array[]
150
     */
151
    public function getLayers()
152
    {
153
        return $this->layers;
154
    }
155
}
156