Passed
Push — ft/fields-refactor ( 34e13a...220f3b )
by Ben
81:47
created

SaveUrlSlugs   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 163
Duplicated Lines 0 %

Test Coverage

Coverage 98.41%

Importance

Changes 0
Metric Value
wmc 21
eloc 55
dl 0
loc 163
ccs 62
cts 63
cp 0.9841
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A saveRecord() 0 31 5
A prependBaseUrlSegment() 0 8 2
A handle() 0 11 3
A deleteIdenticalRedirects() 0 10 3
A createRecord() 0 7 1
A strict() 0 5 1
A cleanupExistingRecords() 0 13 1
A deleteRecord() 0 3 1
A __construct() 0 3 1
A deleteIdenticalRecords() 0 14 3
1
<?php
2
3
namespace Thinktomorrow\Chief\Urls\Application;
4
5
use Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl;
6
use Thinktomorrow\Chief\Urls\UrlRecord;
7
8
class SaveUrlSlugs
9
{
10
    /** @var bool */
11
    private $strict = true;
12
13
    /** @var ProvidesUrl */
14
    private $model;
15
16
    private $existingRecords;
17
18 64
    public function __construct(ProvidesUrl $model)
19
    {
20 64
        $this->model = $model;
21 64
    }
22
23
    /**
24
     * Saving urls slugs in strict mode prevents identical urls to be automatically removed.
25
     * When set to false, this would remove the identical url records.
26
     *
27
     * @param bool $strict
28
     * @return $this
29
     */
30 4
    public function strict(bool $strict = true)
31
    {
32 4
        $this->strict = $strict;
33
34 4
        return $this;
35
    }
36
37 64
    public function handle(array $slugs): void
38
    {
39 64
        $this->existingRecords = UrlRecord::getByModel($this->model);
40
41 64
        foreach ($slugs as $locale => $slug) {
42 63
            if (!$slug) {
43 2
                $this->deleteRecord($locale);
44 2
                continue;
45
            }
46
47 63
            $this->saveRecord($locale, $this->prependBaseUrlSegment($slug, $locale));
48
        }
49 64
    }
50
51 2
    private function deleteRecord(string $locale)
52
    {
53 2
        return $this->saveRecord($locale, null);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveRecord($locale, null) targeting Thinktomorrow\Chief\Urls...eUrlSlugs::saveRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
54
    }
55
56 63
    private function saveRecord(string $locale, ?string $slug)
57
    {
58
        // Existing ones for this locale?
59
        $nonRedirectsWithSameLocale = $this->existingRecords->filter(function ($record) use ($locale) {
60
            return (
61 20
                $record->locale == $locale &&
62 20
                !$record->isRedirect()
63
            );
64 63
        });
65
66
        // If slug entry is left empty, all existing records will be deleted
67 63
        if (!$slug) {
68
            $nonRedirectsWithSameLocale->each(function ($existingRecord) {
69 2
                $existingRecord->delete();
70 2
            });
71
72 2
            return;
73
        }
74
75 63
        $this->cleanupExistingRecords($locale, $slug);
76
77
        // If slug entry is left empty, all existing records will be deleted
78 63
        if ($nonRedirectsWithSameLocale->isEmpty()) {
79 61
            $this->createRecord($locale, $slug);
80 61
            return;
81
        }
82
83
        // Only replace the existing records that differ from the current passed slugs
84
        $nonRedirectsWithSameLocale->each(function ($existingRecord) use ($slug) {
85 17
            if ($existingRecord->slug != $slug) {
86 16
                $existingRecord->replaceAndRedirect(['slug' => $slug]);
87
            }
88 17
        });
89 17
    }
90
91 61
    private function createRecord($locale, $slug)
92
    {
93 61
        UrlRecord::create([
94 61
            'locale'              => $locale,
95 61
            'slug'                => $slug,
96 61
            'model_type'          => $this->model->getMorphClass(),
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not exist on Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl. ( Ignorable by Annotation )

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

96
            'model_type'          => $this->model->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
97 61
            'model_id'            => $this->model->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
98
        ]);
99 61
    }
100
101
    /**
102
     * @param string $locale
103
     * @param string|null $slug
104
     */
105 63
    private function cleanupExistingRecords(string $locale, string $slug): void
106
    {
107
        // In the case where we have any redirects that match the given slug, we need to
108
        // remove the redirect record in favour of the newly added one.
109 63
        $this->deleteIdenticalRedirects($this->existingRecords, $locale, $slug);
110
111 63
        $sameExistingRecords = UrlRecord::where('slug', $slug)->where('locale', $locale)->get();
112
113
        // Also delete any redirects that match this locale and slug but are related to another model
114 63
        $this->deleteIdenticalRedirects($sameExistingRecords, $locale, $slug);
115
116
        // Also delete any urls that match this locale and slug but are related to another model
117 63
        $this->deleteIdenticalRecords($sameExistingRecords);
118 63
    }
119
120
    /**
121
     * Remove any redirects owned by this model that equal the new slug.
122
     *
123
     * @param $existingRecords
124
     * @param $locale
125
     * @param $slug
126
     */
127 63
    private function deleteIdenticalRedirects($existingRecords, $locale, $slug): void
128
    {
129
        $existingRecords->filter(function ($record) use ($locale) {
130
            return (
131 18
                $record->locale == $locale &&
132 18
                $record->isRedirect()
133
            );
134
        })->each(function ($existingRecord) use ($slug) {
135 4
            if ($existingRecord->slug == $slug) {
136 2
                $existingRecord->delete();
137
            }
138 63
        });
139 63
    }
140
141 63
    private function deleteIdenticalRecords($existingRecords): void
142
    {
143 63
        if($this->strict) return;
144
145
        // The old homepage url should be removed since this is no longer in effect.
146
        // In case of any redirect to this old homepage, the last used redirect is now back in effect.
147
        $existingRecords->reject(function($existingRecord){
148
            return (
149 1
                $existingRecord->model_type == $this->model->getMorphClass() &&
150 1
                $existingRecord->model_id == $this->model->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Urls\ProvidesUrl\ProvidesUrl suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
151
        })->each(function($existingRecord){
152
153
            // TODO: if there is a redirect to this page, we'll take this one as the new url
154
            $existingRecord->delete();
155 4
        });
156 4
    }
157
158
    /**
159
     * @param string $slug
160
     * @param $locale
161
     * @return string
162
     */
163 63
    private function prependBaseUrlSegment(string $slug, $locale): string
164
    {
165 63
        $slugWithBaseSegment = $this->model->baseUrlSegment($locale) . '/' . $slug;
166 63
        $slugWithBaseSegment = trim($slugWithBaseSegment, '/');
167
168
        // If slug with base segment is empty string, it means that the passed slug was probably a "/" character.
169
        // so we'll want to return it in case the base segment is not added.
170 63
        return $slugWithBaseSegment ?: '/';
171
    }
172
}
173