Passed
Push — master ( 005cd7...e6687a )
by Vasyl
02:26
created

StrTokenGenerator::variablesTokens()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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