Passed
Push — master ( 806331...6aab10 )
by Richard
04:40 queued 18s
created

Block   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 86
c 2
b 0
f 0
dl 0
loc 292
rs 8.8798
wmc 44

26 Methods

Rating   Name   Duplication   Size   Complexity  
A component() 0 3 1
A carboniseDates() 0 7 5
A key() 0 3 1
A filterComponent() 0 3 1
A jsonSerialize() 0 18 4
A count() 0 3 1
A editableBridge() 0 8 3
A valid() 0 3 1
A offsetExists() 0 2 1
A view() 0 19 2
A render() 0 6 1
A __get() 0 15 4
A current() 0 3 1
A isEmpty() 0 2 1
A offsetSet() 0 5 2
A content() 0 3 1
A has() 0 2 1
A processStoryblokKeys() 0 4 1
A offsetUnset() 0 2 1
A __construct() 0 22 4
A uuid() 0 3 1
A __toString() 0 3 1
A offsetGet() 0 2 2
A next() 0 3 1
A rewind() 0 3 1
A getMethods() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
// TODO - add defaults / null object
4
// TODO - date casting to Carbon
5
6
/////// blocks might be keyed with numbers from storyblok.
7
/// we might need to be able to access specific ones - reordering content will change the number
8
/// we either need a method to find a specific child (by component name)
9
/// or a Block Trait to key content by the child component name
10
11
namespace Riclep\Storyblok;
12
13
use Carbon\Carbon;
14
use Exception;
15
use Illuminate\Support\Collection;
16
use Illuminate\Support\Str;
17
use ReflectionClass;
18
use ReflectionMethod;
19
use Riclep\Storyblok\Traits\ProcessesBlocks;
20
use Riclep\Storyblok\Traits\RequestsStories;
21
22
abstract class Block implements \JsonSerializable, \Iterator, \ArrayAccess, \Countable
23
{
24
	use ProcessesBlocks;
25
	use RequestsStories; // TODO cab we dynamically add this just for blocks that make requests
26
27
	protected $_uid;
28
	protected $component;
29
	protected $content;
30
	protected $meta;
31
	private $iteratorIndex = 0;
32
33
	/**
34
	 * Converts Storyblok’s API response into something usable by us. Each block becomes a class
35
	 * with the Storyblok UUID, the component name and any content under it’s own content key
36
	 *
37
	 * @param $block
38
	 * @param $key
39
	 */
40
	public function __construct($block, $key)
41
	{
42
		if (array_key_exists('content', $block)) {
43
			// child story so go straight to the contents but store a few useful meta items from the Story
44
			$this->processStoryblokKeys($block['content']);
45
			$this->meta = array_intersect_key($block, array_flip(['name', 'created_at', 'published_at', 'slug', 'full_slug']));
46
		} else {
47
			$this->processStoryblokKeys($block);
48
		}
49
50
		$this->content->transform(function($item, $key) {
51
			return $this->processBlock($item, $key);
52
		});
53
54
		$this->carboniseDates();
55
56
		if ($this->getMethods()->contains('transform')) {
57
			$this->transform();
0 ignored issues
show
Bug introduced by
The method transform() does not exist on Riclep\Storyblok\Block. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

57
			$this->/** @scrutinizer ignore-call */ 
58
          transform();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
58
		}
59
60
		if (method_exists($this, 'init')) {
61
			$this->init();
62
		}
63
	}
64
65
	private function processStoryblokKeys($block) {
66
		$this->_uid = $block['_uid'] ?? null;
67
		$this->component = $block['component'] ?? null;
68
		$this->content = collect(array_diff_key($block, array_flip(['_editable', '_uid', 'component', 'plugin'])));
69
	}
70
71
	protected function view() {
72
		$views[] = 'storyblok.blocks.uuid.' . $this->_uid;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$views was never initialized. Although not strictly required by PHP, it is generally a good practice to add $views = array(); before regardless.
Loading history...
73
		$segments = explode('/', rtrim(app()->make('Page')->slug(), '/'));
74
		// creates an array of dot paths for each path segment
75
		// site.com/this/that/them becomes:
76
		// this.that.them
77
		// this.that
78
		// this
79
		$views[] = 'storyblok.blocks.' . implode('.', $segments) . '.=' . $this->component;
80
		$views[] = 'storyblok.blocks.' . implode('.', $segments) . '.' . $this->component;
81
		while (count($segments) > 1) {
82
			array_pop($segments);
83
			$views[] = 'storyblok.blocks.' . implode('.', $segments) . '.' . $this->component;
84
		}
85
86
		$views[] = 'storyblok.blocks.' . $this->component;
87
		$views[] = 'storyblok.blocks.default';
88
89
		return $views;
90
	}
91
92
	/**
93
	 * Finds the view used to display this block’s content
94
	 * it will always fall back to a default view.
95
	 *
96
	 */
97
	public function render()
98
	{
99
		return view()->first(
100
			$this->view(),
101
			[
102
				'block' => $this
103
			]
104
		);
105
	}
106
107
	/**
108
	 * @return mixed
109
	 */
110
	public function editableBridge()
111
	{
112
		// TODO - make this work
113
		if (!$this->slug) {
0 ignored issues
show
Bug Best Practice introduced by
The property slug does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
114
			return $this->_editable ?: null;
0 ignored issues
show
Bug Best Practice introduced by
The property _editable does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
115
		}
116
117
		return null;
118
	}
119
120
	/**
121
	 * @return mixed
122
	 */
123
	public function content()
124
	{
125
		return $this->content;
126
	}
127
128
	/**
129
	 * @return mixed
130
	 */
131
	public function component()
132
	{
133
		return $this->component;
134
	}
135
136
	public function filterComponent($componentName) {
137
		return $this->content->filter(function ($block) use ($componentName) {
138
			return $block->component === $componentName;
139
		});
140
	}
141
142
	/**
143
	 * Checks if the content contains the specified item
144
	 *
145
	 * @param string $key
146
	 * @return mixed
147
	 */
148
	public function has($key) {
149
		return $this->content->has($key);
150
	}
151
152
	/**
153
	 * @return mixed
154
	 */
155
	public function uuid()
156
	{
157
		return $this->_uid;
158
	}
159
160
	/**
161
	 * Returns a rendered version of the block’s view
162
	 *
163
	 * @return string
164
	 */
165
	public function __toString()
166
	{
167
		return $this->content()->toJson(JSON_PRETTY_PRINT);
168
	}
169
170
	/**
171
	 * Determines how this object is converted to JSON
172
	 *
173
	 * @return mixed
174
	 */
175
	public function jsonSerialize()
176
	{
177
		if (property_exists($this, 'excluded')) {
178
			$content = $this->content->except($this->excluded);
0 ignored issues
show
Bug Best Practice introduced by
The property excluded does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
179
		} else {
180
			$content = $this->content;
181
		}
182
183
		$attributes = [];
184
185
		// get the appended attributes
186
		if (property_exists($this, 'appends')) {
187
			foreach ($this->appends as $attribute) {
0 ignored issues
show
Bug Best Practice introduced by
The property appends does not exist on Riclep\Storyblok\Block. Since you implemented __get, consider adding a @property annotation.
Loading history...
188
				$attributes[$attribute] = $this->{$attribute};
189
			}
190
		}
191
192
		return $content->merge(collect($attributes));
193
	}
194
195
196
	/**
197
	 * As all content sits under the content property we can ease access to these with a magic getter
198
	 * it looks inside the content collection for a matching key and returns it.
199
	 *
200
	 * If an accessor has been created for an existing or ‘new’ content item it will be returned.
201
	 *
202
	 * @param $name
203
	 * @return mixed
204
	 * @throws \ReflectionException
205
	 */
206
	public function __get($name) {
207
		$accessor = 'get' . Str::studly($name) . 'Attribute';
208
209
		if ($this->getMethods()->contains($accessor)) {
210
			return $this->$accessor();
211
		}
212
213
		try {
214
			if ($this->has($name)) {
215
				return $this->content[$name];
216
			}
217
218
			return false;
219
		} catch (Exception $e) {
220
			return 'Caught exception: ' .  $e->getMessage();
221
		}
222
	}
223
224
	/**
225
	 * Gets all the public methods for a class and it’s descendants
226
	 *
227
	 * @return Collection
228
	 * @throws \ReflectionException
229
	 */
230
	public function getMethods() {
231
		$class = new ReflectionClass($this);
232
		return collect($class->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED))->transform(function($item) {
233
			return $item->name;
234
		});
235
	}
236
237
	protected function carboniseDates() {
238
		$properties = get_object_vars($this);
239
240
		if (array_key_exists('dates', $properties)) {
241
			foreach ($properties['dates'] as $date) {
242
				if ($this->content->has($date)) {
243
					$this->content[$date] = $this->content[$date] ? Carbon::parse($this->content[$date]) : null;
244
				}
245
			}
246
		}
247
	}
248
249
	public function isEmpty() {
250
		return $this->content->isEmpty();
251
	}
252
253
	/*
254
	 * Methods for Iterator trait allowing us to foreach over a collection of
255
	 * Blocks and return their content. This makes accessing child content
256
	 * in Blade much cleaner
257
	 * */
258
	public function current()
259
	{
260
		return $this->content[$this->iteratorIndex];
261
	}
262
263
	public function next()
264
	{
265
		$this->iteratorIndex++;
266
	}
267
268
	public function rewind()
269
	{
270
		$this->iteratorIndex = 0;
271
	}
272
273
	public function key()
274
	{
275
		return $this->iteratorIndex;
276
	}
277
278
	public function valid()
279
	{
280
		return isset($this->content[$this->iteratorIndex]);
281
	}
282
283
	/*
284
	 * Methods for ArrayAccess Trait - allows us to dig straight down to the content collection
285
	 * when calling a key on the Object
286
	 * */
287
	public function offsetSet($offset, $value) {
288
		if (is_null($offset)) {
289
			$this->content[] = $value;
290
		} else {
291
			$this->content[$offset] = $value;
292
		}
293
	}
294
295
	public function offsetExists($offset) {
296
		return isset($this->content[$offset]);
297
	}
298
299
	public function offsetUnset($offset) {
300
		unset($this->content[$offset]);
301
	}
302
303
	public function offsetGet($offset) {
304
		return isset($this->content[$offset]) ? $this->content[$offset] : null;
305
	}
306
307
308
	/*
309
	 * Countable trait
310
	 * */
311
	public function count()
312
	{
313
		return $this->content->count();
314
	}
315
316
}