Passed
Push — master ( 98d705...005cd7 )
by Vasyl
02:07
created

StrTokenGenerator::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: fomvasss
5
 * Date: 26.10.18
6
 * Time: 03:21
7
 */
8
9
namespace Fomvasss\LaravelStrTokens;
10
11
use App\Models\MetaTag;
0 ignored issues
show
Bug introduced by
The type App\Models\MetaTag was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Carbon\Carbon;
13
use Illuminate\Database\Eloquent\Collection;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Model was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
 
16
class StrTokenGenerator
17
{
18
    /**
19
     * The Laravel application instance.
20
     *
21
     * @var \Illuminate\Foundation\Application
0 ignored issues
show
Bug introduced by
The type Illuminate\Foundation\Application was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
     */
23
    protected $app;
24
    
25
    /**
26
     * The Laravel application configs.
27
     * 
28
     * @var array 
29
     */
30
    protected $config;
31
    
32
    /**
33
     * @var string
34
     */
35
    protected $text = '';
36
37
    /**
38
     * @var null
39
     */
40
    protected $date = null;
41
42
    /**
43
     * @var null
44
     */
45
    protected $entity = null;
46
47
    /**
48
     * @var bool
49
     */
50
    protected $clearEmptyTokens = true;
51
52
    /**
53
     * StrTokenGenerator constructor.
54
     */
55
    public function __construct($app = null)
56
    {
57
        if (!$app) {
58
            $app = app();   //Fallback when $app is not given
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

58
            $app = /** @scrutinizer ignore-call */ app();   //Fallback when $app is not given
Loading history...
59
        }
60
        $this->app = $app;
61
62
        $this->config = $this->app['config'];
63
    }
64
    
65
    /**
66
     * @param string $text
67
     * @return StrTokenGenerator
68
     */
69
    public function setText(string $text = ''): self
70
    {
71
        $this->text = $text;
72
73
        return $this;
74
    }
75
76
    /**
77
     * @param Carbon $date
78
     * @return StrTokenGenerator
79
     */
80
    public function setDate(Carbon $date): self
81
    {
82
        $this->date = $date;
0 ignored issues
show
Documentation Bug introduced by
It seems like $date of type Carbon\Carbon is incompatible with the declared type null of property $date.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
83
84
        return $this;
85
    }
86
87
    /**
88
     * @param Model $entity
89
     * @return StrTokenGenerator
90
     */
91
    public function setEntity(Model $entity): self
92
    {
93
        $this->entity = $entity;
0 ignored issues
show
Documentation Bug introduced by
It seems like $entity of type Illuminate\Database\Eloquent\Model is incompatible with the declared type null of property $entity.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94
95
        return $this;
96
    }
97
98
    /**
99
     * @return StrTokenGenerator
100
     */
101
    public function doNotClearEmptyTokens(): self
102
    {
103
        $this->clearEmptyTokens = false;
104
105
        return $this;
106
    }
107
108
    /**
109
     * @return StrTokenGenerator
110
     */
111
    public function clearEmptyTokens(): self
112
    {
113
        $this->clearEmptyTokens = true;
114
115
        return $this;
116
    }
117
118
    /**
119
     * @return string
120
     */
121
    public function replace(): string
122
    {
123
        $groupTokens = $this->tokenScan($this->text);
124
        $replacements = [];
125
126
        foreach ($groupTokens as $key => $attributes) {
127
128
            if ($key === 'date') {
129
                $replacements += $this->dateTokens($attributes);
130
131
            } elseif ($key === 'config') {
132
                $replacements += $this->configTokens($attributes);
133
134
            } elseif ($key === 'var') {
135
                $replacements += $this->variablesTokens($attributes);
136
137
            } elseif ($this->entity && strtolower($key) === snake_case(class_basename($this->entity))) {
138
                $replacements += $this->eloquentModelTokens($attributes, $key);
139
140
            // For related taxonomy: https://github.com/fomvasss/laravel-taxonomy
141
            // and you set preffix in your relation methods - "tx"
142
            } elseif ($this->entity && substr($key, 0, 2) === 'tx') {
143
                $replacements += $this->eloquentModelTokens($attributes, $key);
144
            }
145
146
            if ($this->clearEmptyTokens) {
147
                $replacements += array_fill_keys($attributes, '');
148
            }
149
        }
150
151
        $attributes = array_keys($replacements);
152
        $values = array_values($replacements);
153
154
        return str_replace($attributes, $values, $this->text);
155
    }
156
157
    /**
158
     * Taken from the best CMS - Drupal :)
159
     * https://api.drupal.org/api/drupal/includes%21token.inc/function/token_scan/7.x
160
     * preg_match_all('/\[([^\]:]*):([^\]]*)\]/', $tokenStr, $matches);
161
     *
162
     * @param $text
163
     * @return array
164
     */
165
    private function tokenScan(string $text): array
166
    {
167
168
        // Matches tokens with the following pattern: [$type:$name]
169
        // $type and $name may not contain  [ ] characters.
170
        // $type may not contain : or whitespace characters, but $name may.
171
        preg_match_all('/
172
            \\[             # [ - pattern start
173
            ([^\\s\\[\\]:]*)  # match $type not containing whitespace : [ or ]
174
            :              # : - separator
175
            ([^\\[\\]]*)     # match $name not containing [ or ]
176
            \\]             # ] - pattern end
177
            /x', $text, $matches);
178
        $types = $matches[1];
179
        $tokens = $matches[2];
180
181
        // Iterate through the matches, building an associative array containing
182
        // $tokens grouped by $types, pointing to the version of the token found in
183
        // the source text. For example, $results['node']['title'] = '[node:title]';
184
        $results = [];
185
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
186
            $results[$types[$i]][$tokens[$i]] = $matches[0][$i];
187
        }
188
189
        return $results;
190
    }
191
192
    /**
193
     * @param array $tokens
194
     * @param string $type
195
     * @return array
196
     */
197
    protected function eloquentModelTokens(array $tokens, string $type): array
198
    {
199
        $replacements = [];
200
201
        foreach ($tokens as $key => $original) {
202
            $function = explode(':', $key)[0];
203
            $strTokenMethod = camel_case('str_token_'.$function);
204
205
            // Exists token generate method (defined user)
206
            if (method_exists($this->entity, $strTokenMethod)) {
207
208
                $replacements[$original] = $this->entity->{$strTokenMethod}($this->entity, ...explode(':', $key));
209
210
            // Exists relation function (defined user)
211
            } elseif (method_exists($this->entity, $function)) {
212
213
                $newOriginal = str_replace("$type:", '', $original);
214
215
                if ($this->entity->{$function} instanceof Model) {
216
                    $tm = new static();
217
218
                    $replacements[$original] = $tm->setText($newOriginal)->setEntity($this->entity->{$function})->replace();
219
                } elseif ($this->entity->{$function} instanceof Collection && ($firstRelatedEntity = $this->entity->{$function}->first())) {
220
                    $tm = new static();
221
222
                    $replacements[$original] = $tm->setText($newOriginal)->setEntity($firstRelatedEntity)->replace();
223
                }
224
            // Is field model
225
            } else {
226
                // TODO: make and check available model fields
227
                $replacements[$original] = $this->entity->{$key};
228
            }
229
        }
230
231
        return $replacements;
232
    }
233
234
    /**
235
     * TODO: require https://github.com/fomvasss/laravel-variables
236
     *
237
     * @param $tokens
238
     * @return array
239
     */
240
    protected function variablesTokens(array $tokens): array
241
    {
242
        $replacements = [];
243
244
        foreach ($tokens as $name => $original) {
245
            $replacements[$original] = var_get($name);
0 ignored issues
show
Bug introduced by
The function var_get was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

245
            $replacements[$original] = /** @scrutinizer ignore-call */ var_get($name);
Loading history...
246
        }
247
248
        return $replacements;
249
    }
250
251
    /**
252
     * @param array $tokens
253
     * @return array
254
     */
255
    protected function configTokens(array $tokens): array
256
    {
257
        $replacements = [];
258
259
        $disable = $this->config->get('str-tokens.disable_configs', []);
260
261
        foreach ($tokens as $name => $original) {
262
            if (! Helpers::strIs($disable, $name)) {
263
                $res = $this->config->get($name, '');
264
                $replacements[$original] = is_string($res) ? $res : '';
265
            }
266
        }
267
268
        return $replacements;
269
    }
270
271
    /**
272
     * @param $tokens
273
     * @return array
274
     */
275
    protected function dateTokens(array $tokens):array
276
    {
277
        $this->date = $this->date ?: Carbon::now();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->date ?: Carbon\Carbon::now() can also be of type Carbon\Carbon. However, the property $date is declared as type null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
278
        $replacements = [];
279
280
        foreach ($tokens as $name => $original) {
281
            if ($name === 'raw') {
282
                $replacements[$original] = $this->date;
283
            } else {
284
                $format = $this->config->get('str-tokens.date.formats.'.$name, 'D, m/d/Y - H:i');
285
                $replacements[$original] = $this->date->format($format);
286
            }
287
        }
288
289
        return $replacements;
290
    }
291
}