Completed
Push — master ( ddf2b7...f0be57 )
by Maxime
02:38
created

SyncFlatten::handle()   B

Complexity

Conditions 10
Paths 88

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
nc 88
nop 1
dl 0
loc 40
ccs 0
cts 33
cp 0
crap 110
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Distilleries\Contentful\Commands\Sync;
4
5
use Distilleries\Contentful\Models\Release;
6
use stdClass;
7
use Exception;
8
use Illuminate\Console\Command;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Facades\DB;
11
use Distilleries\Contentful\Models\Locale;
12
use Symfony\Component\Console\Helper\ProgressBar;
13
use Distilleries\Contentful\Repositories\AssetsRepository;
14
use Distilleries\Contentful\Repositories\EntriesRepository;
15
16
class SyncFlatten extends Command
17
{
18
    use Traits\SyncTrait;
19
20
    /**
21
     * Number of entries to fetch per pagination.
22
     *
23
     * @var integer
24
     */
25
    const PER_BATCH = 50;
26
27
    /**
28
     * {@inheritdoc}
29
     */
30
    protected $signature = 'contentful:sync-flatten {--preview} {--no-switch} {--truncate} {--multi}';
31
32
    /**
33
     * {@inheritdoc}
34
     */
35
    protected $description = 'Map and persist previously synced Contentful data';
36
37
    /**
38
     * Assets repository instance.
39
     *
40
     * @var \Distilleries\Contentful\Repositories\AssetsRepository
41
     */
42
    protected $assets;
43
44
    /**
45
     * Entries repository instance.
46
     *
47
     * @var \Distilleries\Contentful\Repositories\EntriesRepository
48
     */
49
    protected $entries;
50
51
    /**
52
     * MapEntries command constructor.
53
     *
54
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
55
     */
56
    public function __construct()
57
    {
58
        parent::__construct();
59
60
        $this->assets = new AssetsRepository;
61
62
        $this->entries = new EntriesRepository;
63
    }
64
65
    protected function canSwitch(): bool
66
    {
67
        $bool = $this->option('no-switch');
68
        return !empty($bool) ? false : true;
69
    }
70
71
    protected function withTruncate(): bool
72
    {
73
        $bool = $this->option('truncate');
74
        return !empty($bool) ? true : false;
75
    }
76
77
    protected function isPreview(): bool
78
    {
79
        $bool = $this->option('preview');
80
        return !empty($bool) ? true : false;
81
    }
82
83
    protected function isMultiThread(): bool
84
    {
85
        $bool = $this->option('multi');
86
        return !empty($bool) ? true : false;
87
    }
88
89
    /**
90
     * Execute the console command.
91
     *
92
     * @return void
93
     */
94
    public function handle(Release $release)
95
    {
96
        $isPreview = $this->isPreview();
97
98
        if ($this->canSwitch()) {
99
            $this->call('contentful:sync-switch', $isPreview ? ['--preview'] : []);
100
        }
101
102
        if ($isPreview) {
103
            use_contentful_preview();
104
        }
105
106
        if ($this->withTruncate()) {
107
            $this->line('Truncate Contentful related tables');
108
            $this->assets->truncateRelatedTables();
109
            $this->entries->truncateRelatedTables();
110
        }
111
112
        try {
113
            $this->line('Map and persist synced data');
114
            if($this->isMultiThread()){
115
                $release = $this->getCurrentRelease($release);
116
                $this->flattenSyncedDataMultiThread($release);
117
            }else{
118
                $this->flattenSyncedData();
119
            }
120
121
122
        } catch (Exception $e) {
123
            echo PHP_EOL;
124
            $this->error($e->getMessage());
125
            return;
126
        }
127
128
        if ($this->canSwitch()) {
129
            if ($this->canSwitch()) {
130
                $this->call('contentful:sync-switch', $isPreview ? ['--preview', '--live'] : ['--live']);
131
            }
132
        }
133
    }
134
135
    /**
136
     * Map and persist synced data.
137
     *
138
     * @return void
139
     * @throws \Exception
140
     */
141
    protected function flattenSyncedData()
142
    {
143
        $page = 1;
144
        $paginator = DB::table('sync_entries')->paginate(static::PER_BATCH, ['*'], 'page', $page);
145
        $locales = Locale::all();
146
147
        $bar = $this->createProgressBar($paginator->total());
148
        while ($paginator->isNotEmpty()) {
149
            foreach ($paginator->items() as $item) {
150
                $bar->setMessage('Map entry ID: ' . $item->contentful_id);
151
                $this->mapItemToContentfulModel($item, $locales);
0 ignored issues
show
Bug introduced by
It seems like $locales defined by \Distilleries\Contentful\Models\Locale::all() on line 145 can also be of type array<integer,object<Ill...base\Eloquent\Builder>>; however, Distilleries\Contentful\...ItemToContentfulModel() does only seem to accept object<Illuminate\Support\Collection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152
                $bar->advance();
153
            }
154
155
            $page++;
156
            $paginator = DB::table('sync_entries')->paginate(static::PER_BATCH, ['*'], 'page', $page);
157
        }
158
        $bar->finish();
159
160
        echo PHP_EOL;
161
    }
162
163
    protected function flattenSyncedDataMultiThread(Release $release)
164
    {
165
166
        $locales = Locale::all();
167
        $bar = $this->createProgressBar(DB::table('sync_entries')->count());
168
169
        try {
170
            $this->updateFromOtherThread($bar);
171
            $items = collect();
172
173
            DB::transaction(function () use ($release, & $items) {
174
                $items = DB::table('sync_entries')
175
                    ->whereNull('release_id')
176
                    ->take(static::PER_BATCH)
177
                    ->lockForUpdate()
178
                    ->get();
179
180
                DB::table('sync_entries')
181
                    ->whereIn('id', $items->pluck('id')->toArray())
182
                    ->lockForUpdate()
183
                    ->update(['release_id' => $release->getKey()]);
184
            });
185
        } catch (Exception $e) {
186
            //
187
        }
188
189
        $items->each(function ($item, $key) use ($locales, $bar) {
1 ignored issue
show
Bug introduced by
The variable $items does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
190
            $bar->setMessage('Map entry ID: ' . $item->contentful_id);
191
            $this->mapItemToContentfulModel($item, $locales);
0 ignored issues
show
Bug introduced by
It seems like $locales defined by \Distilleries\Contentful\Models\Locale::all() on line 166 can also be of type array<integer,object<Ill...base\Eloquent\Builder>>; however, Distilleries\Contentful\...ItemToContentfulModel() does only seem to accept object<Illuminate\Support\Collection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
192
            $bar->advance();
193
        });
194
195
        $bar->finish();
196
197
    }
198
199
    /**
200
     * Update progress from other threads.
201
     *
202
     * @return void
203
     */
204
    protected function updateFromOtherThread($bar)
205
    {
206
        $bar->setProgress((DB::table('sync_entries'))->whereNotNull('release_id')->count());
207
    }
208
209
    /**
210
     * Get current release.
211
     *
212
     */
213
    protected function getCurrentRelease(Release $release)
214
    {
215
        return $release->where('current', true)->get()->first();
216
    }
217
218
    /**
219
     * Create custom progress bar.
220
     *
221
     * @param  integer $total
222
     * @return \Symfony\Component\Console\Helper\ProgressBar
223
     */
224
    protected function createProgressBar(int $total): ProgressBar
225
    {
226
        $bar = $this->output->createProgressBar($total);
227
228
        $bar->setFormat("%message%" . PHP_EOL . " %current%/%max% [%bar%] %percent:3s%%");
229
230
        return $bar;
231
    }
232
233
    /**
234
     * Map and persist given sync_entries item.
235
     *
236
     * @param  \stdClass $item
237
     * @param \Illuminate\Support\Collection $locales
238
     * @return void
239
     * @throws \Exception
240
     */
241
    protected function mapItemToContentfulModel(stdClass $item, Collection $locales)
242
    {
243
        $entry = json_decode($item->payload, true);
244
245
        if ($item->contentful_type === 'asset') {
246
            $this->assets->toContentfulModel($entry, $locales);
247
        } else {
248
            $this->entries->toContentfulModel($entry, $locales);
249
        }
250
    }
251
}
252