Completed
Push — master ( 070fe8...6dba77 )
by Richard
15s queued 11s
created

BlockSyncCommand::handle()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 11
c 1
b 0
f 1
nc 6
nop 0
dl 0
loc 20
rs 9.9
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 attributes to Laravel Block classes.';
30
    /**
31
     * @var Filesystem
32
     */
33
    private Filesystem $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
            $fields = [];
154
            foreach ($component['schema'] as $name => $data) {
0 ignored issues
show
introduced by
$name is overwriting one of the parameters of this function.
Loading history...
155
                if ( ! $this->isIgnoredType($data['type'])) {
156
                    $fields[$name] = $this->convertToPhpType($data['type']);
157
                }
158
            }
159
160
            return $fields;
161
        } else {
162
            $this->error("Please set your management token in the Storyblok config file");
163
            return [];
164
        }
165
    }
166
167
    /**
168
     * Convert Storyblok types to PHP native types for proper type-hinting
169
     *
170
     * @param $type
171
     * @return string
172
     */
173
    protected function convertToPhpType($type)
174
    {
175
        switch ($type) {
176
            case "bloks":
177
                return "array";
178
            default:
179
                return "string";
180
        }
181
    }
182
183
    /**
184
     * There are certain Storyblok types that are not useful to model in our component classes. We can use this to
185
     * filter those types out.
186
     *
187
     * @param $type
188
     * @return bool
189
     */
190
    protected function isIgnoredType($type)
191
    {
192
        $ignored = ['section'];
193
194
        return in_array($type, $ignored);
195
    }
196
197
}
198