Completed
Push — master ( 938d31...c86a83 )
by Richard
17s queued 15s
created

BlockSyncCommand::createStoryblokCompontent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 18
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
			$managementClient = new \Storyblok\ManagementClient(config('storyblok.oauth_token'));
146
147
			$components = 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

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