StrTokenGenerator::setVars()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 2
c 1
b 0
f 1
dl 0
loc 5
rs 10
cc 1
nc 1
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 Carbon\Carbon;
12
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...
13
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...
14
use Illuminate\Support\Str;
15
16
class StrTokenGenerator
17
{
18
    /** @var \Illuminate\Foundation\Application The Laravel application instance. */
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...
19
    protected $app;
20
    
21
    /** @var mixed The Laravel application configs. */
22
    protected $config;
23
    
24
    /** @var string */
25
    protected $text = '';
26
27
    /** @var null */
28
    protected $date = null;
29
30
    /** @var null */
31
    protected $entity = null;
32
    
33
    /** @var array */
34
    protected $entities = [];
35
36
    /** @var array */
37
    protected $vars = [];
38
39
    /** @var bool */
40
    protected $clearEmptyTokens = true;
41
42
    /**
43
     * StrTokenGenerator constructor.
44
     */
45
    public function __construct($app = null)
46
    {
47
        if (!$app) {
48
            $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

48
            $app = /** @scrutinizer ignore-call */ app();   //Fallback when $app is not given
Loading history...
49
        }
50
        $this->app = $app;
51
52
        $this->config = $this->app['config'];
53
    }
54
    
55
    /**
56
     * @param string $text
57
     * @return StrTokenGenerator
58
     */
59
    public function setText(string $text = ''): self
60
    {
61
        $this->text = $text;
62
63
        return $this;
64
    }
65
66
    /**
67
     * @param Carbon $date
68
     * @return StrTokenGenerator
69
     */
70
    public function setDate(Carbon $date): self
71
    {
72
        $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...
73
74
        return $this;
75
    }
76
77
    /**
78
     * @param Model $entity
79
     * @return StrTokenGenerator
80
     */
81
    public function setEntity(Model $entity): self
82
    {
83
        $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...
84
85
        return $this;
86
    }
87
88
    /**
89
     * @param array $entities|Illuminate\Database\Eloquent\Model
90
     * @return \Fomvasss\LaravelStrTokens\StrTokenGenerator
91
     * @throws \Exception
92
     */
93
    public function setEntities(array $entities): self
94
    {
95
        foreach ($entities as $key => $entity) {
96
            $this->ensureValidEntity($entity);
97
        }
98
        
99
        $this->entities = $entities;
100
        
101
        return $this;
102
    }
103
104
    /**
105
     * @param array $vars
106
     * @return $this
107
     */
108
    public function setVars(array $vars): self
109
    {
110
        $this->vars = $vars;
111
112
        return $this;
113
    }
114
115
    /**
116
     * @param string $key
117
     * @param $value
118
     * @return $this
119
     */
120
    public function setVar(string $key, $value): self
121
    {
122
        $this->vars[$key] = $value;
123
124
        return $this;
125
    }
126
    
127
    /**
128
     * @return StrTokenGenerator
129
     */
130
    public function doNotClearEmptyTokens(): self
131
    {
132
        $this->clearEmptyTokens = false;
133
134
        return $this;
135
    }
136
137
    /**
138
     * @return StrTokenGenerator
139
     */
140
    public function clearEmptyTokens(): self
141
    {
142
        $this->clearEmptyTokens = true;
143
144
        return $this;
145
    }
146
147
    /**
148
     * @return string
149
     */
150
    public function replace(): string
151
    {
152
        $groupTokens = $this->tokenScan($this->text);
153
        $replacements = [];
154
155
        foreach ($groupTokens as $key => $attributes) {
156
157
            if ($key === 'date') {
158
                $replacements += $this->dateTokens($attributes);
159
160
            } elseif ($key === 'config') {
161
                $replacements += $this->configTokens($attributes);
162
163
            } elseif ($key === 'var') {
164
                $replacements += $this->varTokens($attributes);
165
166
            } elseif ($this->entity && strtolower($key) === Str::snake(class_basename($this->entity))) {
167
                $replacements += $this->eloquentModelTokens($this->entity, $attributes, $key);
168
169
            // For related taxonomy: https://github.com/fomvasss/laravel-taxonomy
170
            // and you set preffix in your relation methods - "tx"
171
            } elseif ($this->entity && substr($key, 0, 2) === 'tx') {
172
                $replacements += $this->eloquentModelTokens($this->entity, $attributes, $key);
173
                
174
            } elseif (in_array($key, array_keys($this->entities))) {
175
                $eloquentModel = $this->entities[$key];
176
                $replacements += $this->eloquentModelTokens($eloquentModel, $attributes, $key);
177
            }
178
179
            if ($this->clearEmptyTokens) {
180
                $replacements += array_fill_keys($attributes, '');
181
            }
182
        }
183
184
        $attributes = array_keys($replacements);
185
        $values = array_values($replacements);
186
187
        return str_replace($attributes, $values, $this->text);
188
    }
189
190
    /**
191
     * Token scan with CMS Drupal :)
192
     * https://api.drupal.org/api/drupal/includes%21token.inc/function/token_scan/7.x
193
     * preg_match_all('/\[([^\]:]*):([^\]]*)\]/', $tokenStr, $matches);
194
     *
195
     * @param $text
196
     * @return array
197
     */
198
    private function tokenScan(string $text): array
199
    {
200
201
        // Matches tokens with the following pattern: [$type:$name]
202
        // $type and $name may not contain  [ ] characters.
203
        // $type may not contain : or whitespace characters, but $name may.
204
        preg_match_all('/
205
            \\[             # [ - pattern start
206
            ([^\\s\\[\\]:]*)  # match $type not containing whitespace : [ or ]
207
            :              # : - separator
208
            ([^\\[\\]]*)     # match $name not containing [ or ]
209
            \\]             # ] - pattern end
210
            /x', $text, $matches);
211
        $types = $matches[1];
212
        $tokens = $matches[2];
213
214
        // Iterate through the matches, building an associative array containing
215
        // $tokens grouped by $types, pointing to the version of the token found in
216
        // the source text. For example, $results['node']['title'] = '[node:title]';
217
        $results = [];
218
        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...
219
            $results[$types[$i]][$tokens[$i]] = $matches[0][$i];
220
        }
221
222
        return $results;
223
    }
224
225
    /**
226
     * @param array $tokens
227
     * @param string $type
228
     * @return array
229
     */
230
    protected function eloquentModelTokens(Model $eloquentModel, array $tokens, string $type): array
231
    {
232
        $replacements = [];
233
234
        foreach ($tokens as $key => $original) {
235
            $function = explode(':', $key)[0];
236
            $strTokenMethod = Str::camel('str_token_'.$function);
237
238
            // Exists token generate method (defined user)
239
            if (method_exists($eloquentModel, $strTokenMethod)) {
240
241
                $replacements[$original] = $eloquentModel->{$strTokenMethod}($eloquentModel, ...explode(':', $key));
242
243
            // Exists relation function (defined user)
244
            } elseif (method_exists($eloquentModel, $function)) {
245
246
                $newOriginal = str_replace("$type:", '', $original);
247
248
                if ($eloquentModel->{$function} instanceof Model) {
249
                    $tm = new static();
250
251
                    $replacements[$original] = $tm->setText($newOriginal)->setEntity($eloquentModel->{$function})->replace();
252
                } elseif ($eloquentModel->{$function} instanceof Collection && ($firstRelatedEntity = $eloquentModel->{$function}->first())) {
253
                    $tm = new static();
254
255
                    $replacements[$original] = $tm->setText($newOriginal)->setEntity($firstRelatedEntity)->replace();
256
                }
257
            // Is field model
258
            } else {
259
                // TODO: make and check available model fields
260
                $replacements[$original] = $eloquentModel->{$key};
261
            }
262
        }
263
264
        return $replacements;
265
    }
266
267
    /**
268
     * @param array $tokens
269
     * @return array
270
     */
271
    protected function configTokens(array $tokens): array
272
    {
273
        $replacements = [];
274
275
        $disable = $this->config->get('str-tokens.disable_configs', []);
276
277
        foreach ($tokens as $name => $original) {
278
            if (! Str::is($disable, $name)) {
279
                $res = $this->config->get($name, '');
280
                $replacements[$original] = is_string($res) ? $res : '';
281
            }
282
        }
283
284
        return $replacements;
285
    }
286
287
    /**
288
     * @param array $tokens
289
     * @return array
290
     */
291
    protected function varTokens(array $tokens): array
292
    {
293
        $replacements = [];
294
295
        foreach ($tokens as $name => $original) {
296
            $res = $this->vars[$name] ?? '';
297
            $replacements[$original] = is_string($res) ? $res : '';
298
        }
299
300
        return $replacements;
301
    }
302
303
    /**
304
     * @param $tokens
305
     * @return array
306
     */
307
    protected function dateTokens(array $tokens):array
308
    {
309
        $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...
310
        $replacements = [];
311
312
        foreach ($tokens as $name => $original) {
313
            if ($name === 'raw') {
314
                $replacements[$original] = $this->date;
315
            } else {
316
                $format = $this->config->get('str-tokens.date.formats.'.$name, 'D, m/d/Y - H:i');
317
                $replacements[$original] = $this->date->format($format);
318
            }
319
        }
320
321
        return $replacements;
322
    }
323
324
    /**
325
     * @param $entity
326
     * @throws \Exception
327
     */
328
    protected function ensureValidEntity($entity)
329
    {
330
        if (! $entity instanceof Model) {
331
            throw new \Exception("StrToken Entity must by instance of '" . Model::class . "'. Current instance of '" . gettype($entity) . "'");
332
        }
333
    }
334
}