Passed
Push — master ( 24c314...71620f )
by Richard
02:09
created

Block::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
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
	public $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
	 */
39
	public function __construct($block)
40
	{
41
		if (array_key_exists('content', $block)) {
42
			// child story so go straight to the contents but store a few useful meta items from the Story
43
			$this->processStoryblokKeys($block['content']);
44
			$this->meta = array_intersect_key($block, array_flip(['name', 'created_at', 'published_at', 'slug', 'full_slug']));
45
		} else {
46
			$this->processStoryblokKeys($block);
47
		}
48
49
		$this->content->transform(function($item, $key) {
50
			return $this->processBlock($item, $key);
51
		});
52
53
		$this->carboniseDates();
54
55
		if ($this->getMethods()->contains('transform')) {
56
			$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

56
			$this->/** @scrutinizer ignore-call */ 
57
          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...
57
		}
58
59
		if (method_exists($this, 'init')) {
60
			$this->init();
61
		}
62
	}
63
64
	private function processStoryblokKeys($block) {
65
		$this->_uid = $block['_uid'] ?? null;
66
		$this->component = $block['component'] ?? null;
67
		$this->content = collect(array_diff_key($block, array_flip(['_editable', '_uid', 'component', 'plugin'])));
68
	}
69
70
	/**
71
	 * @return mixed
72
	 */
73
	public function editableBridge()
74
	{
75
		// TODO - make this work
76
		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...
77
			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...
78
		}
79
80
		return null;
81
	}
82
83
	/**
84
	 * @return mixed
85
	 */
86
	public function content()
87
	{
88
		return $this->content;
89
	}
90
91
	/**
92
	 * @return mixed
93
	 */
94
	public function component()
95
	{
96
		return $this->component;
97
	}
98
99
	public function filterComponent($componentName) {
100
		return $this->content->filter(function ($block) use ($componentName) {
101
			return $block->component === $componentName;
102
		});
103
	}
104
105
	/**
106
	 * Checks if the content contains the specified item
107
	 *
108
	 * @param string $key
109
	 * @return mixed
110
	 */
111
	public function has($key) {
112
		return $this->content->has($key);
113
	}
114
115
	/**
116
	 * @return mixed
117
	 */
118
	public function uuid()
119
	{
120
		return $this->_uid;
121
	}
122
123
	/**
124
	 * Returns a rendered version of the block’s view
125
	 *
126
	 * @return string
127
	 */
128
	public function __toString()
129
	{
130
		return $this->content()->toJson(JSON_PRETTY_PRINT);
131
	}
132
133
	/**
134
	 * Determines how this object is converted to JSON
135
	 *
136
	 * @return mixed
137
	 */
138
	public function jsonSerialize()
139
	{
140
		if (property_exists($this, 'excluded')) {
141
			$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...
142
		} else {
143
			$content = $this->content;
144
		}
145
146
		$attributes = [];
147
148
		// get the appended attributes
149
		if (property_exists($this, 'appends')) {
150
			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...
151
				$attributes[$attribute] = $this->{$attribute};
152
			}
153
		}
154
155
		return $content->merge(collect($attributes));
156
	}
157
158
159
	/**
160
	 * As all content sits under the content property we can ease access to these with a magic getter
161
	 * it looks inside the content collection for a matching key and returns it.
162
	 *
163
	 * If an accessor has been created for an existing or ‘new’ content item it will be returned.
164
	 *
165
	 * @param $name
166
	 * @return mixed
167
	 * @throws \ReflectionException
168
	 */
169
	public function __get($name) {
170
		$accessor = 'get' . Str::studly($name) . 'Attribute';
171
172
		if ($this->getMethods()->contains($accessor)) {
173
			return $this->$accessor();
174
		}
175
176
		try {
177
			if ($this->has($name)) {
178
				return $this->content[$name];
179
			}
180
181
			return false;
182
		} catch (Exception $e) {
183
			return 'Caught exception: ' .  $e->getMessage();
184
		}
185
	}
186
187
	/**
188
	 * Gets all the public methods for a class and it’s descendants
189
	 *
190
	 * @return Collection
191
	 * @throws \ReflectionException
192
	 */
193
	public function getMethods() {
194
		$class = new ReflectionClass($this);
195
		return collect($class->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED))->transform(function($item) {
196
			return $item->name;
197
		});
198
	}
199
200
	protected function carboniseDates() {
201
		$properties = get_object_vars($this);
202
203
		if (array_key_exists('dates', $properties)) {
204
			foreach ($properties['dates'] as $date) {
205
				if ($this->content->has($date)) {
206
					$this->content[$date] = Carbon::parse($this->content[$date]) ?: $this->content[$date];
207
				}
208
			}
209
		}
210
	}
211
212
	public function isEmpty() {
213
		return $this->content->isEmpty();
214
	}
215
216
	/*
217
	 * Methods for Iterator trait allowing us to foreach over a collection of
218
	 * Blocks and return their content. This makes accessing child content
219
	 * in Blade much cleaner
220
	 * */
221
	public function current()
222
	{
223
		return $this->content[$this->iteratorIndex];
224
	}
225
226
	public function next()
227
	{
228
		$this->iteratorIndex++;
229
	}
230
231
	public function rewind()
232
	{
233
		$this->iteratorIndex = 0;
234
	}
235
236
	public function key()
237
	{
238
		return $this->iteratorIndex;
239
	}
240
241
	public function valid()
242
	{
243
		return isset($this->content[$this->iteratorIndex]);
244
	}
245
246
	/*
247
	 * Methods for ArrayAccess Trait - allows us to dig straight down to the content collection
248
	 * when calling a key on the Object
249
	 * */
250
	public function offsetSet($offset, $value) {
251
		if (is_null($offset)) {
252
			$this->content[] = $value;
253
		} else {
254
			$this->content[$offset] = $value;
255
		}
256
	}
257
258
	public function offsetExists($offset) {
259
		return isset($this->content[$offset]);
260
	}
261
262
	public function offsetUnset($offset) {
263
		unset($this->content[$offset]);
264
	}
265
266
	public function offsetGet($offset) {
267
		return isset($this->content[$offset]) ? $this->content[$offset] : null;
268
	}
269
270
271
	/*
272
	 * Countable trait
273
	 * */
274
	public function count()
275
	{
276
		return $this->content->count();
277
	}
278
279
}