Passed
Pull Request — master (#11)
by
unknown
02:53
created

BlockSyncCommand::createStoryblokCompontent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 14
rs 10
1
<?php
2
3
namespace Riclep\Storyblok\Console;
4
5
use Barryvdh\Reflection\DocBlock;
6
use Barryvdh\Reflection\DocBlock\Context;
7
use Barryvdh\Reflection\DocBlock\Serializer;
8
use Barryvdh\Reflection\DocBlock\Tag;
9
use Illuminate\Console\Command;
10
use Illuminate\Filesystem\Filesystem;
11
use Illuminate\Support\Facades\Cache;
12
use Illuminate\Support\Str;
13
14
class BlockSyncCommand extends Command
15
{
16
17
	/**
18
	 * The name and signature of the console command.
19
	 *
20
	 * @var string
21
	 */
22
	protected $signature = 'ls:sync {component?} {--path=app/Storyblok/Blocks/}';
23
24
	/**
25
	 * The console command description.
26
	 *
27
	 * @var string
28
	 */
29
	protected $description = 'Sync Storyblok fields to Laravel Block class properties.';
30
	/**
31
	 * @var Filesystem
32
	 */
33
	private $files;
34
35
	/**
36
	 * Create a new command instance.
37
	 * @param  Filesystem  $files
38
	 */
39
	public function __construct(Filesystem $files)
40
	{
41
		parent::__construct();
42
43
		$this->files = $files;
44
	}
45
46
	/**
47
	 * Execute the console command.
48
	 *
49
	 * @return void
50
	 */
51
	public function handle()
52
	{
53
		$components = [];
54
		if ($this->argument('component')) {
55
			$components = [
56
				[
57
					'class' => $this->argument('component'),
58
					'component' => Str::of($this->argument('component'))->kebab(),
0 ignored issues
show
Bug introduced by
It seems like $this->argument('component') can also be of type string[]; however, parameter $string of Illuminate\Support\Str::of() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

58
					'component' => Str::of(/** @scrutinizer ignore-type */ $this->argument('component'))->kebab(),
Loading history...
59
				]
60
			];
61
		} else {
62
			// get all components
63
			if ($this->confirm("Do you wish to update all components in {$this->option('path')}?")) {
64
				$components = $this->getAllComponents();
65
			}
66
		}
67
68
		foreach ($components as $component) {
69
			$this->info("Updating {$component['component']}");
70
			$this->updateComponent($component);
71
		}
72
	}
73
74
	/**
75
	 * @return \Illuminate\Support\Collection
76
	 */
77
	protected function getAllComponents()
78
	{
79
		$path = $this->option('path');
80
81
		$files = collect($this->files->allFiles($path));
82
83
		return $files->map(function ($file) {
84
			return [
85
				'class' => Str::of($file->getFilename())->replace('.php', ''),
86
				'component' => Str::of($file->getFilename())->replace('.php', '')->kebab(),
87
			];
88
		});
89
	}
90
91
	private function updateComponent($component): void
92
	{
93
		$rootNamespace = "App\Storyblok\Blocks";
94
		$class = "{$rootNamespace}\\{$component['class']}";
95
96
		$reflection = new \ReflectionClass($class);
97
		$namespace = $reflection->getNamespaceName();
98
		$path = $this->option('path');
99
		$originalDoc = $reflection->getDocComment();
100
101
		$filepath = $path.$component['class'].'.php';
102
103
		$phpdoc = new DocBlock($reflection, new Context($namespace));
104
105
		$tags = $phpdoc->getTagsByName('property-read');
106
107
		// Clear old attributes
108
		foreach ($tags as $tag) {
109
			$phpdoc->deleteTag($tag);
110
		}
111
112
		// Add new attributes
113
		$fields = $this->getComponentFields($component['component']);
114
		foreach ($fields as $field => $type) {
115
			$tagLine = trim("@property-read {$type} {$field}");
116
			$tag = Tag::createInstance($tagLine, $phpdoc);
117
118
			$phpdoc->appendTag($tag);
119
		}
120
121
		// Add default description if none exists
122
		if ( ! $phpdoc->getText()) {
123
			$phpdoc->setText("Class representation for Storyblok {$component['component']} component.");
124
		}
125
126
		// Write to file
127
		if ($this->files->exists($filepath)) {
128
			$serializer = new Serializer();
129
			$updatedBlock = $serializer->getDocComment($phpdoc);
130
131
			$content = $this->files->get($filepath);
132
133
			$content = str_replace($originalDoc, $updatedBlock, $content);
134
135
			$this->files->replace($filepath, $content);
136
			$this->info('Component updated successfully.');
137
		} else {
138
			$this->error('Component not yet created...');
139
		}
140
	}
141
142
	protected function getComponentFields($name)
143
	{
144
		if (config('storyblok.oauth_token')) {
145
			$components = Cache::remember('lsb-compontent-list', 600, function () {
146
				$managementClient = new \Storyblok\ManagementClient(config('storyblok.oauth_token'));
147
148
				return collect($managementClient->get('spaces/'.config('storyblok.space_id').'/components')->getBody()['components']);
0 ignored issues
show
Bug introduced by
The method getBody() does not exist on stdClass. ( Ignorable by Annotation )

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

148
				return collect($managementClient->get('spaces/'.config('storyblok.space_id').'/components')->/** @scrutinizer ignore-call */ getBody()['components']);

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...
149
			});
150
151
			$component = $components->firstWhere('name', $name);
152
153
			if( ! $component ){
154
				$this->error("Storyblok component [{$name}] does not exist.");
155
156
				if ($this->confirm('Do you want to create it now?')) {
157
					$this->createStoryblokCompontent($name);
158
				}
159
			}
160
161
			$fields = [];
162
			foreach ($component['schema'] as $name => $data) {
0 ignored issues
show
introduced by
$name is overwriting one of the parameters of this function.
Loading history...
163
				if ( ! $this->isIgnoredType($data['type'])) {
164
					$fields[$name] = $this->convertToPhpType($data['type']);
165
				}
166
			}
167
168
			return $fields;
169
		} else {
170
			$this->error("Please set your management token in the Storyblok config file");
171
			return [];
172
		}
173
	}
174
175
	/**
176
	 * Create a new Storyblok component with given name
177
	 *
178
	 * @param  $component_name
179
	 */
180
	protected function createStoryblokCompontent($component_name){
181
			$managementClient = new \Storyblok\ManagementClient(config('storyblok.oauth_token'));
182
			$payload = [
183
				"component" =>  [
184
					"name" =>  $component_name,
185
					"display_name" =>  str::of( str_replace('-', ' ' ,$component_name) )->ucfirst(),
186
						// "schema" =>  [],
187
						// "is_root" =>  false,
188
						// "is_nestable" =>  true
189
				]
190
			];
191
			$managementClient->post('spaces/'.config('storyblok.space_id').'/components/', $payload)->getBody();
192
193
			$this->info("Storyblok component created");
194
	}
195
196
	/**
197
	 * Convert Storyblok types to PHP native types for proper type-hinting
198
	 *
199
	 * @param $type
200
	 * @return string
201
	 */
202
	protected function convertToPhpType($type)
203
	{
204
		switch ($type) {
205
			case "bloks":
206
				return "array";
207
			default:
208
				return "string";
209
		}
210
	}
211
212
	/**
213
	 * There are certain Storyblok types that are not useful to model in our component classes. We can use this to
214
	 * filter those types out.
215
	 *
216
	 * @param $type
217
	 * @return bool
218
	 */
219
	protected function isIgnoredType($type)
220
	{
221
		$ignored = ['section'];
222
223
		return in_array($type, $ignored);
224
	}
225
226
}
227