Completed
Pull Request — 1.2 (#12)
by David
05:26
created

ChainRenderer   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 214
Duplicated Lines 18.69 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 4
dl 40
loc 214
rs 9.1199
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A render() 0 8 2
F getRenderer() 22 62 22
B getRendererDebugMessage() 18 40 11
A initRenderersList() 0 13 4
A setTemplateRendererInstanceName() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ChainRenderer 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 ChainRenderer, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * Copyright (c) 2013 David Negrier
4
 *
5
 * See the file LICENSE.txt for copying permission.
6
 */
7
8
namespace Mouf\Html\Renderer;
9
10
use Psr\Container\ContainerInterface;
11
use Psr\SimpleCache\CacheInterface;
12
13
/**
14
 * This class is a renderer that renders objects using other renderers.
15
 * This renderer will automatically detect the renderers to be included.
16
 * They must extend the ChainableRendererInterface interface.
17
 *
18
 * @author David Négrier <[email protected]>
19
 */
20
class ChainRenderer implements CanSetTemplateRendererInterface
21
{
22
23
    /**
24
     * @var ChainableRendererInterface|null
25
     */
26
    private $templateRenderer;
27
    /**
28
     * @var ChainableRendererInterface[]
29
     */
30
    private $packageRenderers = [];
31
    /**
32
     * @var ChainableRendererInterface[]
33
     */
34
    private $customRenderers = [];
35
36
    private $cacheService;
37
38
    private $initDone = false;
39
    /**
40
     * @var string[]
41
     */
42
    private $customRendererInstanceNames;
43
    /**
44
     * @var string
45
     */
46
    private $templateRendererInstanceName;
47
    /**
48
     * @var string[]
49
     */
50
    private $packageRendererInstanceNames;
51
    /**
52
     * @var string
53
     */
54
    private $uniqueName;
55
    /**
56
     * @var ContainerInterface
57
     */
58
    private $container;
59
60
    /**
61
     *
62
     * @param string[] $customRendererInstanceNames An array of names of custom renderers (container identifiers)
63
     * @param string[] $packageRendererInstanceNames An array of names of package renderers (container identifiers)
64
     * @param CacheInterface $cacheService This service is used to speed up the mapping between the object and the template.
65
     * @param string $uniqueName The unique name for this instance (used for caching purpose)
66
     */
67
    public function __construct(ContainerInterface $container, array $customRendererInstanceNames, array $packageRendererInstanceNames, CacheInterface $cacheService, string $uniqueName)
68
    {
69
        $this->container = $container;
70
        $this->customRendererInstanceNames = $customRendererInstanceNames;
71
        $this->packageRendererInstanceNames = $packageRendererInstanceNames;
72
        $this->cacheService = $cacheService;
73
        $this->uniqueName = $uniqueName;
74
    }
75
76
    /**
77
     * (non-PHPdoc)
78
     * @see \Mouf\Html\Renderer\RendererInterface::render()
79
     */
80
    public function render($object, string $context = null): void
81
    {
82
        $renderer = $this->getRenderer($object, $context);
83
        if ($renderer == null) {
84
            throw new NoRendererFoundException("Renderer not found. Unable to find renderer for object of class '".get_class($object)."'. Path tested: ".$this->getRendererDebugMessage($object, $context));
85
        }
86
        $renderer->render($object, $context);
87
    }
88
89
    /**
90
     * @param object $object
91
     * @param string|null $context
92
     * @return ChainableRendererInterface
93
     */
94
    private function getRenderer($object, string $context = null): ChainableRendererInterface
95
    {
96
        $cacheKey = "chainRendererByClass_".md5($this->uniqueName."/".$this->templateRendererInstanceName."/".get_class($object)."/".$context);
97
98
        $cachedInstanceName = $this->cacheService->get($cacheKey);
99
        if ($cachedInstanceName !== null) {
100
            return $this->container->get($cachedInstanceName);
101
        }
102
103
        $this->initRenderersList();
104
105
        $isCachable = true;
106
        $foundRenderer = null;
107
        $source = null;
0 ignored issues
show
Unused Code introduced by
$source is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
108
        $foundInstanceName = null;
109
110
        do {
111 View Code Duplication
            foreach ($this->customRenderers as $instanceName => $renderer) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
112
                $result = $renderer->canRender($object, $context);
113
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CANNOT_RENDER_OBJECT) {
114
                    $isCachable = false;
115
                }
116
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CAN_RENDER_CLASS) {
117
                    $foundRenderer = $renderer;
118
                    $foundInstanceName = $instanceName;
119
                    break 2;
120
                }
121
            }
122
123
            if ($this->templateRendererInstanceName && !$this->templateRenderer) {
124
                $this->templateRenderer = $this->container->get($this->templateRendererInstanceName);
125
            }
126
            if ($this->templateRenderer) {
127
                $result = $this->templateRenderer->canRender($object, $context);
128
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CANNOT_RENDER_OBJECT) {
129
                    $isCachable = false;
130
                }
131
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CAN_RENDER_CLASS) {
132
                    $foundRenderer = $this->templateRenderer;
133
                    break;
134
                }
135
            }
136
137 View Code Duplication
            foreach ($this->packageRenderers as $instanceName => $renderer) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
138
                $result = $renderer->canRender($object, $context);
139
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CANNOT_RENDER_OBJECT) {
140
                    $isCachable = false;
141
                }
142
                if ($result == ChainableRendererInterface::CAN_RENDER_OBJECT || $result == ChainableRendererInterface::CAN_RENDER_CLASS) {
143
                    $foundRenderer = $renderer;
144
                    $foundInstanceName = $instanceName;
145
                    break 2;
146
                }
147
            }
148
        } while (false);
149
150
        if ($isCachable && $foundRenderer) {
151
            $this->cacheService->set($cacheKey, $foundInstanceName);
152
        }
153
154
        return $foundRenderer;
155
    }
156
157
    /**
158
     * Returns a string explaining the steps done to find the renderer.
159
     *
160
     * @param  object $object
161
     * @param  string $context
162
     * @return string
163
     */
164
    private function getRendererDebugMessage($object, string $context = null): string
165
    {
166
        $debugMessage = '';
167
168
        $this->initRenderersList();
169
170
        do {
171 View Code Duplication
            foreach ($this->customRenderers as $renderer) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
172
                /* @var $renderer ChainableRendererInterface */
173
174
                $debugMessage .= $renderer->debugCanRender($object, $context);
175
                $result = $renderer->canRender($object, $context);
176
                if ($result === ChainableRendererInterface::CAN_RENDER_OBJECT || $result === ChainableRendererInterface::CAN_RENDER_CLASS) {
177
                    break 2;
178
                }
179
            }
180
181
            /* @var $renderer ChainableRendererInterface */
182
            if ($this->templateRenderer) {
183
                $debugMessage .= $this->templateRenderer->debugCanRender($object, $context);
184
                $result = $this->templateRenderer->canRender($object, $context);
185
                if ($result === ChainableRendererInterface::CAN_RENDER_OBJECT || $result === ChainableRendererInterface::CAN_RENDER_CLASS) {
186
                    break;
187
                }
188
            }
189
190 View Code Duplication
            foreach ($this->packageRenderers as $renderer) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
191
                /* @var $renderer ChainableRendererInterface */
192
193
                $debugMessage .= $renderer->debugCanRender($object, $context);
194
                $result = $renderer->canRender($object, $context);
195
                if ($result === ChainableRendererInterface::CAN_RENDER_OBJECT || $result === ChainableRendererInterface::CAN_RENDER_CLASS) {
196
                    break 2;
197
                }
198
            }
199
200
        } while (false);
201
		
202
		return $debugMessage;
203
	}
204
	
205
	/**
206
	 * Initializes the renderers list (from cache if available)
207
	 */
208
	private function initRenderersList(): void {
209
        if (!$this->initDone) {
210
            foreach ($this->customRendererInstanceNames as $instanceName) {
211
                $this->customRenderers[$instanceName] = $this->container->get($instanceName);
212
            }
213
            foreach ($this->packageRendererInstanceNames as $instanceName) {
214
                $this->packageRenderers[$instanceName] = $this->container->get($instanceName);
215
            }
216
217
            // Note: We ignore template renderers on purpose.
218
            $this->initDone = true;
219
        }
220
	}
221
222
    /**
223
     * Sets the renderer associated to the template.
224
     * There should be only one if these renderers.
225
     * It is the role of the template to subscribe to this renderer.
226
     *
227
     * @param string $templateRendererInstanceName The name of the template renderer in the container
228
     */
229
    public function setTemplateRendererInstanceName(string $templateRendererInstanceName): void
230
    {
231
        $this->templateRendererInstanceName = $templateRendererInstanceName;
232
    }
233
}
234