ExtendedSluggableBehavior::queryMethods()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4286
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
3
namespace ItBlaster\TranslationBundle\Behavior;
4
5
class ExtendedSluggableBehavior extends TranslationBehavior
6
{
7
    // default parameters value
8
    protected $parameters = array(
9
        'add_cleanup'     => 'true',
10
        'slug_column'     => 'slug',
11
        'slug_pattern'    => '',
12
        'replace_pattern' => '/\W+/', // Tip: use '/[^\\pL\\d]+/u' instead if you're in PHP5.3
13
        'replacement'     => '-',
14
        'separator'       => '-',
15
        'permanent'       => 'false',
16
        'scope_column'    => '',
17
        'primary_string'  => '',
18
    );
19
20
    /**
21
     * Add the slug_column to the current table
22
     */
23
    public function modifyTable()
24
    {
25
        $table = $this->getTable();
26
        $primary_string = $this->getParameter('primary_string');
27
28
        if (!$primary_string) {
29
            $this->exceptionError('Need set parameter "primary_string" in table '.$table->getName());
30
        }
31
32
        if (!$table->hasColumn($primary_string)) {
33
            $this->exceptionError('Not found column "'.$primary_string.'" in table '.$table->getName());
34
        }
35
36
        if (!$this->getTable()->containsColumn($this->getParameter('slug_column'))) {
0 ignored issues
show
Deprecated Code introduced by
The method Table::containsColumn() has been deprecated with message: use hasColumn() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
37
            $this->getTable()->addColumn(array(
38
                'name' => $this->getParameter('slug_column'),
39
                'type' => 'VARCHAR',
40
                'size' => 255
41
            ));
42
            // add a unique to column
43
            $unique = new \Unique($this->getColumnForParameter('slug_column'));
0 ignored issues
show
Bug introduced by
It seems like $this->getColumnForParameter('slug_column') targeting Behavior::getColumnForParameter() can also be of type object<Column>; however, Index::__construct() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
44
            $unique->setName($this->getTable()->getCommonName() . '_slug');
45
            $unique->addColumn($this->getTable()->getColumn($this->getParameter('slug_column')));
46
            if ($this->getParameter('scope_column')) {
47
                $unique->addColumn($this->getTable()->getColumn($this->getParameter('scope_column')));
48
            }
49
            $this->getTable()->addUnique($unique);
50
        }
51
    }
52
53
    /**
54
     * Get the getter of the column of the behavior
55
     *
56
     * @return string The related getter, e.g. 'getSlug'
57
     */
58
    protected function getColumnGetter()
59
    {
60
        return 'get' . $this->getColumnForParameter('slug_column')->getPhpName();
61
    }
62
63
    /**
64
     * Get the setter of the column of the behavior
65
     *
66
     * @return string The related setter, e.g. 'setSlug'
67
     */
68
    protected function getColumnSetter()
69
    {
70
        return 'set' . $this->getColumnForParameter('slug_column')->getPhpName();
71
    }
72
73
    /**
74
     * Add code in ObjectBuilder::preSave
75
     *
76
     * @return string The code to put at the hook
77
     */
78
    public function preSave(\PHP5ObjectBuilder $builder)
79
    {
80
        $const = $builder->getColumnConstant($this->getColumnForParameter('slug_column'));
0 ignored issues
show
Bug introduced by
It seems like $this->getColumnForParameter('slug_column') can be null; however, getColumnConstant() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
81
        $pattern = $this->getParameter('slug_pattern');
82
        $script = "
83
if (\$this->isColumnModified($const) && \$this->{$this->getColumnGetter()}()) {
84
    \$this->{$this->getColumnSetter()}(\$this->makeSlugUnique(\$this->{$this->getColumnGetter()}()));";
85
86
        if ($pattern && false === $this->booleanValue($this->getParameter('permanent'))) {
87
            $script .= "
88
} elseif (";
89
            $count = preg_match_all('/{([a-zA-Z]+)}/', $pattern, $matches, PREG_PATTERN_ORDER);
90
91
            foreach ($matches[1] as $key => $match) {
92
                $columnName = $this->underscore(ucfirst($match));
93
                $column = $this->getTable()->getColumn($columnName);
94
                if ((null == $column) && $this->getTable()->hasBehavior('symfony_i18n')) {
95
                    $i18n = $this->getTable()->getBehavior('symfony_i18n');
96
                    $column = $i18n->getI18nTable()->getColumn($columnName);
97
                }
98
                if (null == $column) {
99
                    throw new \InvalidArgumentException(sprintf('The pattern %s is invalid  the column %s is not found', $pattern, $match));
100
                }
101
                $columnConst = $builder->getColumnConstant($column);
102
                $script .= "\$this->isColumnModified($columnConst)" . ($key < $count - 1 ? " || " : "");
103
            }
104
105
            $script .= ") {
106
    \$this->{$this->getColumnSetter()}(\$this->createSlug());";
107
        }
108
109
        if (null == $pattern && false === $this->booleanValue($this->getParameter('permanent'))) {
110
            $script .= "
111
} else {
112
    \$this->{$this->getColumnSetter()}(\$this->createSlug());
113
}";
114
        } else {
115
            $script .= "
116
} elseif (!\$this->{$this->getColumnGetter()}()) {
117
    \$this->{$this->getColumnSetter()}(\$this->createSlug());
118
}";
119
        }
120
121
        return $script;
122
    }
123
124
    public function objectMethods(\PHP5ObjectBuilder $builder)
125
    {
126
        $this->builder = $builder;
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
127
        $script = '';
128
        if ('slug' != $this->getParameter('slug_column')) {
129
            $this->addSlugSetter($script);
130
            $this->addSlugGetter($script);
131
        }
132
        $this->addCreateSlug($script);
133
        $this->addCreateRawSlug($script);
134
        if ($this->booleanValue($this->getParameter('add_cleanup'))) {
135
            $this->addCleanupSlugPart($script);
136
        }
137
        $this->addLimitSlugSize($script);
138
        $this->addMakeSlugUnique($script);
139
140
        $this->addSortI18ns($script);
141
        $this->addToSlug($script);
142
143
        return $script;
144
    }
145
146
    protected function addSlugSetter(&$script)
147
    {
148
        $script .= "
149
/**
150
 * Wrap the setter for slug value
151
 *
152
 * @param   string
153
 * @return  " . $this->getTable()->getPhpName() . "
154
 */
155
public function setSlug(\$v)
156
{
157
    return \$this->" . $this->getColumnSetter() . "(\$v);
158
}
159
";
160
    }
161
162
    protected function addSlugGetter(&$script)
163
    {
164
        $script .= "
165
/**
166
 * Wrap the getter for slug value
167
 *
168
 * @return  string
169
 */
170
public function getSlug()
171
{
172
    return \$this->" . $this->getColumnGetter() . "();
173
}
174
";
175
    }
176
177
    protected function addCreateSlug(&$script)
178
    {
179
        $script .= "
180
/**
181
 * Create a unique slug based on the object
182
 *
183
 * @return string The object slug
184
 */
185
protected function createSlug()
186
{
187
    \$slug = \$this->createRawSlug();
188
    \$slug = \$this->limitSlugSize(\$slug);
189
    \$slug = \$this->makeSlugUnique(\$slug);
190
191
    return \$slug;
192
}
193
";
194
    }
195
196
197
    /**
198
     * Метод для формирования Slug
199
     *
200
     * @param $script
201
     */
202
    protected function addToSlug(&$script)
203
    {
204
205
        $primary_string = $this->getParameter('primary_string');
206
        $i18n_languages = $this->getSlugLocales();
207
        $primary_string_column =  count($i18n_languages) ? $primary_string : $this->getColumnForParameter('primary_string');
208
        $get_primary_string = 'get'.(count($i18n_languages) ? $this->CamelCase($primary_string) : $primary_string_column->getPhpName());
209
210
        if(!$primary_string_column) {
211
            $this->exceptionError('Not found column "'.$primary_string.'" in table '.$this->getTable()->getName());
212
        }
213
214
        $toSlug = '
215
/**
216
 * Метод для формирования Slug
217
 *
218
 * @return string
219
 */
220
protected function toSlug() {';
221
222
        //есть языковые версии
223
        if (count($i18n_languages)) {
224
            $languages = 'array(';
225
            foreach ($i18n_languages as $lang) {
226
                $languages.='"'.$lang.'",';
227
            }
228
            $languages.=')';
229
230
            $toSlug .= '
231
        $to_string = $this->isNew() ? "Новая запись" : "";
232
        $languages = '.$languages.';
233
        foreach ($languages as $language) {
234
            $str = $this->setLocale($language)->'.$get_primary_string.'();
235
            if ($str) {
236
                return $str;
237
            }
238
        }
239
        return $to_string;';
240
        } else { //нет языковых версий
241
            $toSlug .= '
242
        return $this->'.$get_primary_string.'() ? $this->'.$get_primary_string.'() : "Новая запись";';
243
        }
244
        $toSlug .= '
245
    }
246
    ';
247
        $script .= $toSlug;
248
    }
249
250
    protected function addCreateRawSlug(&$script)
251
    {
252
        $pattern = $this->getParameter('slug_pattern');
253
        $script .= "
254
/**
255
 * Create the slug from the appropriate columns
256
 *
257
 * @return string
258
 */
259
protected function createRawSlug()
260
{
261
    ";
262
        if ($pattern) {
263
            $script .= "return '" . str_replace(array('{', '}'), array('\' . $this->cleanupSlugPart($this->get', '()) . \''), $pattern) . "';";
264
        } else {
265
            $script .= "return \$this->cleanupSlugPart(\$this->toSlug());";
266
        }
267
        $script .= "
268
}
269
";
270
271
        return $script;
272
    }
273
274
    public function addCleanupSlugPart(&$script)
275
    {
276
        $script .= "
277
/**
278
 * Cleanup a string to make a slug of it
279
 * Removes special characters, replaces blanks with a separator, and trim it
280
 *
281
 * @param     string \$slug        the text to slugify
282
 * @param     string \$replacement the separator used by slug
283
 * @return    string               the slugified text
284
 */
285
protected static function cleanupSlugPart(\$slug, \$replacement = '" . $this->getParameter('replacement') . "')
286
{
287
    \$slug = strtr(\$slug, array(
288
        'А' => 'A',  'Б' => 'B',  'В' => 'V',    'Г' => 'G',    'Д' => 'D', 'Е' => 'E', 'Ё' => 'E',  'Ж' => 'ZH',
289
        'З' => 'Z',  'И' => 'I',  'Й' => 'Y',    'К' => 'K',    'Л' => 'L', 'М' => 'M', 'Н' => 'N',  'О' => 'O',
290
        'П' => 'P',  'Р' => 'R',  'С' => 'S',    'Т' => 'T',    'У' => 'U', 'Ф' => 'F', 'Х' => 'KH', 'Ц' => 'TS',
291
        'Ч' => 'CH', 'Ш' => 'SH', 'Щ' => 'SHCH', 'Ь' => '',     'Ы' => 'Y', 'Ъ' => '',  'Э' => 'E',  'Ю' => 'YU',
292
        'Я' => 'YA', 'а' => 'a',  'б' => 'b',    'в' => 'v',    'г' => 'g', 'д' => 'd', 'е' => 'e',  'ё' => 'e',
293
        'ж' => 'zh', 'з' => 'z',  'и' => 'i',    'й' => 'y',    'к' => 'k', 'л' => 'l', 'м' => 'm',  'н' => 'n',
294
        'о' => 'o',  'п' => 'p',  'р' => 'r',    'с' => 's',    'т' => 't', 'у' => 'u', 'ф' => 'f',  'х' => 'kh',
295
        'ц' => 'ts', 'ч' => 'ch', 'ш' => 'sh',   'щ' => 'shch', 'ь' => '',  'ы' => 'y', 'ъ' => '',   'э' => 'e',
296
        'ю' => 'yu', 'я' => 'ya'
297
    ));
298
299
    // transliterate
300
    if (function_exists('iconv')) {
301
        \$slug = iconv('utf-8', 'us-ascii//TRANSLIT', \$slug);
302
    }
303
304
    // lowercase
305
    if (function_exists('mb_strtolower')) {
306
        \$slug = mb_strtolower(\$slug);
307
    } else {
308
        \$slug = strtolower(\$slug);
309
    }
310
311
    // remove accents resulting from OSX's iconv
312
    \$slug = str_replace(array('\'', '`', '^'), '', \$slug);
313
314
    // replace non letter or digits with separator
315
    \$slug = preg_replace('" . $this->getParameter('replace_pattern') . "', \$replacement, \$slug);
316
317
    // trim
318
    \$slug = trim(\$slug, \$replacement);
319
320
    if (empty(\$slug)) {
321
        return 'n-a';
322
    }
323
324
    return \$slug;
325
}
326
";
327
    }
328
329
    public function addLimitSlugSize(&$script)
330
    {
331
        $size = $this->getColumnForParameter('slug_column')->getSize();
332
        $script .= "
333
334
/**
335
 * Make sure the slug is short enough to accommodate the column size
336
 *
337
 * @param    string \$slug                   the slug to check
338
 * @param    int    \$incrementReservedSpace the number of characters to keep empty
339
 *
340
 * @return string                            the truncated slug
341
 */
342
protected static function limitSlugSize(\$slug, \$incrementReservedSpace = 3)
343
{
344
    // check length, as suffix could put it over maximum
345
    if (strlen(\$slug) > ($size - \$incrementReservedSpace)) {
346
        \$slug = substr(\$slug, 0, $size - \$incrementReservedSpace);
347
    }
348
349
    return \$slug;
350
}
351
";
352
    }
353
354
    public function addMakeSlugUnique(&$script)
355
    {
356
        $script .= "
357
358
/**
359
 * Get the slug, ensuring its uniqueness
360
 *
361
 * @param    string \$slug            the slug to check
362
 * @param    string \$separator       the separator used by slug
363
 * @param    int    \$alreadyExists   false for the first try, true for the second, and take the high count + 1
364
 * @return   string                   the unique slug
365
 */
366
protected function makeSlugUnique(\$slug, \$separator = '" . $this->getParameter('separator') . "', \$alreadyExists = false)
367
{";
368
        $getter = $this->getColumnGetter();
369
        $script .= "
370
    if (!\$alreadyExists) {
371
        \$slug2 = \$slug;
372
    } else {
373
        \$slug2 = \$slug . \$separator;";
374
375
        if (null == $this->getParameter('slug_pattern')) {
376
            $script .= "
377
378
        \$count = " . $this->builder->getStubQueryBuilder()->getClassname() . "::create()
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
379
            ->filterBySlug(\$this->$getter())
380
            ->filterByPrimaryKey(\$this->getPrimaryKey())
381
        ->count();
382
383
        if (1 == \$count) {
384
            return \$this->$getter();
385
        }";
386
        }
387
388
        $script .= "
389
    }
390
391
     \$query = " . $this->builder->getStubQueryBuilder()->getClassname() . "::create('q')
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
392
    ";
393
        $platform = $this->getTable()->getDatabase()->getPlatform();
394
        if ($platform instanceof \PgsqlPlatform) {
395
            $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? '~*' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
396
        } elseif ($platform instanceof \MssqlPlatform) {
397
            $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? 'like' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
398
        } elseif ($platform instanceof \OraclePlatform) {
399
            $script .= "->where((\$alreadyExists ? 'REGEXP_LIKE(' : '') . 'q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? ',' : '=') . ' ?' . (\$alreadyExists ? ')' : ''), \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
400
        } else {
401
            $script .= "->where('q." . $this->getColumnForParameter('slug_column')->getPhpName() . " ' . (\$alreadyExists ? 'REGEXP' : '=') . ' ?', \$alreadyExists ? '^' . \$slug2 . '[0-9]+$' : \$slug2)";
402
        }
403
404
        $script .="->prune(\$this)";
405
406
        if ($this->getParameter('scope_column')) {
407
            $scopeGetter = 'get' . $this->getColumnForParameter('scope_column')->getPhpName();
408
            $script .= "
409
            ->filterBy('{$this->getColumnForParameter('scope_column')->getPhpName()}', \$this->{$scopeGetter}())";
410
        }
411
        // watch out: some of the columns may be hidden by the soft_delete behavior
412
        if ($this->table->hasBehavior('soft_delete')) {
413
            $script .= "
414
        ->includeDeleted()";
415
        }
416
        $script .= "
417
    ;
418
419
    if (!\$alreadyExists) {
420
        \$count = \$query->count();
421
        if (\$count > 0) {
422
            return \$this->makeSlugUnique(\$slug, \$separator, true);
423
        }
424
425
        return \$slug2;
426
    }
427
428
    // Already exists
429
    \$object = \$query
430
        ->addDescendingOrderByColumn('LENGTH(" . $this->getColumnForParameter('slug_column')->getName() . ")')
431
        ->addDescendingOrderByColumn('" . $this->getColumnForParameter('slug_column')->getName() . "')
432
    ->findOne();
433
434
    // First duplicate slug
435
    if (null == \$object) {
436
        return \$slug2 . '1';
437
    }
438
439
    \$slugNum = substr(\$object->" . $getter . "(), strlen(\$slug) + 1);
440
    if ('0' === \$slugNum[0]) {
441
        \$slugNum[0] = 1;
442
    }
443
444
    return \$slug2 . (\$slugNum + 1);
445
}
446
";
447
    }
448
449
    public function queryMethods(\QueryBuilder $builder)
450
    {
451
        $this->builder = $builder;
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
452
        $script = '';
453
454
        if ($this->getParameter('slug_column') != 'slug') {
455
            $this->addFilterBySlug($script);
456
            $this->addFindOneBySlug($script);
457
        }
458
459
        return $script;
460
    }
461
462
    protected function addFilterBySlug(&$script)
463
    {
464
        $script .= "
465
/**
466
 * Filter the query on the slug column
467
 *
468
 * @param     string \$slug The value to use as filter.
469
 *
470
 * @return    " . $this->builder->getStubQueryBuilder()->getClassname() . " The current query, for fluid interface
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
471
 */
472
public function filterBySlug(\$slug)
473
{
474
    return \$this->addUsingAlias(" . $this->builder->getColumnConstant($this->getColumnForParameter('slug_column')) . ", \$slug, Criteria::EQUAL);
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
475
}
476
";
477
    }
478
479
    protected function addFindOneBySlug(&$script)
480
    {
481
        $script .= "
482
/**
483
 * Find one object based on its slug
484
 *
485
 * @param     string \$slug The value to use as filter.
486
 * @param     PropelPDO \$con The optional connection object
487
 *
488
 * @return    " . $this->builder->getStubObjectBuilder()->getClassname() . " the result, formatted by the current formatter
0 ignored issues
show
Bug introduced by
The property builder does not seem to exist. Did you mean additionalBuilders?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
489
 */
490
public function findOneBySlug(\$slug, \$con = null)
491
{
492
    return \$this->filterBySlug(\$slug)->findOne(\$con);
493
}
494
";
495
    }
496
497
    /**
498
     * @param string $string
499
     *
500
     * @return string
501
     */
502
    protected function underscore($string)
503
    {
504
        return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($string, '_', '.')));
505
    }
506
}