Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
327914e4
Commit
327914e4
authored
Aug 11, 2014
by
Klimov Paul
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added automatic generating of unique slug value to `yii\behaviors\Sluggable`
parent
c4b8e045
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
342 additions
and
4 deletions
+342
-4
CHANGELOG.md
framework/CHANGELOG.md
+1
-0
SluggableBehavior.php
framework/behaviors/SluggableBehavior.php
+143
-4
SluggableBehaviorTest.php
tests/unit/framework/behaviors/SluggableBehaviorTest.php
+198
-0
No files found.
framework/CHANGELOG.md
View file @
327914e4
...
...
@@ -173,6 +173,7 @@ Yii Framework 2 Change Log
-
Enh #4566: Added client validation support for image validator (Skysplit, qiangxue)
-
Enh #4581: Added ability to disable url encoding in
`UrlRule`
(tadaszelvys)
-
Enh #4602: Added $key param in ActionColumn buttons Closure call (disem)
-
Enh #4630: Added automatic generating of unique slug value to
`yii\behaviors\Sluggable`
(klimov-paul)
-
Enh: Added support for using sub-queries when building a DB query with
`IN`
condition (qiangxue)
-
Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
-
Enh: Added
`yii\web\UrlManager::addRules()`
to simplify adding new URL rules (qiangxue)
...
...
framework/behaviors/SluggableBehavior.php
View file @
327914e4
...
...
@@ -7,6 +7,8 @@
namespace
yii\behaviors
;
use
yii\base\DynamicModel
;
use
yii\base\Exception
;
use
yii\base\InvalidConfigException
;
use
yii\db\BaseActiveRecord
;
use
yii\helpers\Inflector
;
...
...
@@ -47,6 +49,7 @@ use yii\helpers\Inflector;
* }
* ```
* @author Alexander Kochetov <creocoder@gmail.com>
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class
SluggableBehavior
extends
AttributeBehavior
...
...
@@ -56,11 +59,11 @@ class SluggableBehavior extends AttributeBehavior
*/
public
$slugAttribute
=
'slug'
;
/**
* @var string
the attribute
whose value will be converted into a slug
* @var string
|array the attribute or list of attributes
whose value will be converted into a slug
*/
public
$attribute
;
/**
* @var
string|callable
the value that will be used as a slug. This can be an anonymous function
* @var
mixed
the value that will be used as a slug. This can be an anonymous function
* or an arbitrary value. If the former, the return value of the function will be used as a slug.
* The signature of the function should be as follows,
*
...
...
@@ -72,6 +75,39 @@ class SluggableBehavior extends AttributeBehavior
* ```
*/
public
$value
;
/**
* @var boolean whether to ensure generated slug value to be unique among owner class records.
* If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt
* generating unique slug value from based one until success.
*/
public
$unique
=
false
;
/**
* @var array configuration for slug uniqueness validator. This configuration should not contain validator name
* and validated attributes - only options in format 'name => value' are allowed.
* For example:
* [
* 'filter' => ['type' => 1, 'status' => 2]
* ]
* @see yii\validators\UniqueValidator
*/
public
$uniqueValidatorConfig
=
[];
/**
* @var string|callable slug unique value generator. It is used in case [[unique]] enabled and generated
* slug is not unique. This can be a PHP callable with following signature:
*
* ```php
* function ($baseSlug, $iteration)
* {
* // return uniqueSlug
* }
* ```
*
* Also one of the following predefined values can be used:
* - 'increment' - adds incrementing suffix to the base slug
* - 'uniqueid' - adds part of uniqueId hash string to the base slug
* - 'timestamp' - adds current UNIX timestamp to the base slug
*/
public
$uniqueSlugGenerator
=
'increment'
;
/**
...
...
@@ -96,9 +132,112 @@ class SluggableBehavior extends AttributeBehavior
protected
function
getValue
(
$event
)
{
if
(
$this
->
attribute
!==
null
)
{
$this
->
value
=
Inflector
::
slug
(
$this
->
owner
->
{
$this
->
attribute
});
if
(
is_array
(
$this
->
attribute
))
{
$slugParts
=
[];
foreach
(
$this
->
attribute
as
$attribute
)
{
$slugParts
[]
=
Inflector
::
slug
(
$this
->
owner
->
{
$attribute
});
}
$this
->
value
=
implode
(
'-'
,
$slugParts
);
}
else
{
$this
->
value
=
Inflector
::
slug
(
$this
->
owner
->
{
$this
->
attribute
});
}
}
$slug
=
parent
::
getValue
(
$event
);
if
(
$this
->
unique
)
{
$baseSlug
=
$slug
;
$iteration
=
0
;
while
(
!
$this
->
validateSlugUnique
(
$slug
))
{
$iteration
++
;
$slug
=
$this
->
generateUniqueSlug
(
$baseSlug
,
$iteration
);
}
}
return
$slug
;
}
/**
* Checks if given slug value is unique.
* @param string $slug slug value
* @return boolean whether slug is unique.
*/
private
function
validateSlugUnique
(
$slug
)
{
$validator
=
array_merge
(
[
[
'slug'
],
'unique'
,
'targetClass'
=>
get_class
(
$this
->
owner
)
],
$this
->
uniqueValidatorConfig
);
$model
=
DynamicModel
::
validateData
(
compact
(
'slug'
),
[
$validator
]);
return
!
$model
->
hasErrors
();
}
return
parent
::
getValue
(
$event
);
/**
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @return string slug suffix
* @throws \yii\base\InvalidConfigException
*/
private
function
generateUniqueSlug
(
$baseSlug
,
$iteration
)
{
$generator
=
$this
->
uniqueSlugGenerator
;
switch
(
$generator
)
{
case
'increment'
:
return
$this
->
generateUniqueSlugIncrement
(
$baseSlug
,
$iteration
);
case
'uniqueid'
:
return
$this
->
generateUniqueSlugUniqueId
(
$baseSlug
,
$iteration
);
case
'timestamp'
:
return
$this
->
generateSuffixSlugTimestamp
(
$baseSlug
,
$iteration
);
default
:
if
(
is_callable
(
$generator
))
{
return
call_user_func
(
$generator
,
$baseSlug
,
$iteration
);
}
throw
new
InvalidConfigException
(
"Unrecognized slug unique suffix generator '
{
$generator
}
'."
);
}
}
/**
* Generates slug using increment of iteration.
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @return string generated suffix.
*/
protected
function
generateUniqueSlugIncrement
(
$baseSlug
,
$iteration
)
{
return
$baseSlug
.
'-'
.
(
$iteration
+
1
);
}
/**
* Generates slug using unique id.
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @throws \yii\base\Exception
* @return string generated suffix.
*/
protected
function
generateUniqueSlugUniqueId
(
$baseSlug
,
$iteration
)
{
static
$uniqueId
;
if
(
$iteration
<
2
)
{
$uniqueId
=
sha1
(
uniqid
(
get_class
(
$this
),
true
));
}
$subStringLength
=
6
+
$iteration
;
if
(
$subStringLength
>
strlen
(
$uniqueId
))
{
throw
new
Exception
(
'Unique id is exhausted.'
);
}
return
$baseSlug
.
'-'
.
substr
(
$uniqueId
,
0
,
$subStringLength
);
}
/**
* Generates slug using current timestamp.
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @throws \yii\base\Exception
* @return string generated suffix.
*/
protected
function
generateSuffixSlugTimestamp
(
$baseSlug
,
$iteration
)
{
return
$baseSlug
.
'-'
.
(
time
()
+
$iteration
-
1
);
}
}
tests/unit/framework/behaviors/SluggableBehaviorTest.php
0 → 100644
View file @
327914e4
<?php
namespace
yiiunit\framework\behaviors
;
use
Yii
;
use
yiiunit\TestCase
;
use
yii\db\Connection
;
use
yii\db\ActiveRecord
;
use
yii\behaviors\SluggableBehavior
;
/**
* Unit test for [[\yii\behaviors\SluggableBehavior]].
* @see SluggableBehavior
*
* @group behaviors
*/
class
SluggableBehaviorTest
extends
TestCase
{
/**
* @var Connection test db connection
*/
protected
$dbConnection
;
public
static
function
setUpBeforeClass
()
{
if
(
!
extension_loaded
(
'pdo'
)
||
!
extension_loaded
(
'pdo_sqlite'
))
{
static
::
markTestSkipped
(
'PDO and SQLite extensions are required.'
);
}
}
public
function
setUp
()
{
$this
->
mockApplication
([
'components'
=>
[
'db'
=>
[
'class'
=>
'\yii\db\Connection'
,
'dsn'
=>
'sqlite::memory:'
,
]
]
]);
$columns
=
[
'id'
=>
'pk'
,
'name'
=>
'string'
,
'slug'
=>
'string'
,
'category_id'
=>
'integer'
,
];
Yii
::
$app
->
getDb
()
->
createCommand
()
->
createTable
(
'test_slug'
,
$columns
)
->
execute
();
}
public
function
tearDown
()
{
Yii
::
$app
->
getDb
()
->
close
();
parent
::
tearDown
();
}
// Tests :
public
function
testSlug
()
{
$model
=
new
ActiveRecordSluggable
();
$model
->
name
=
'test name'
;
$model
->
validate
();
$this
->
assertEquals
(
'test-name'
,
$model
->
slug
);
}
/**
* @depends testSlug
*/
public
function
testSlugSeveralAttributes
()
{
$model
=
new
ActiveRecordSluggable
();
$model
->
getBehavior
(
'sluggable'
)
->
attribute
=
array
(
'name'
,
'category_id'
);
$model
->
name
=
'test'
;
$model
->
category_id
=
10
;
$model
->
validate
();
$this
->
assertEquals
(
'test-10'
,
$model
->
slug
);
}
/**
* @depends testSlug
*/
public
function
testUniqueByIncrement
()
{
$name
=
'test name'
;
$model
=
new
ActiveRecordSluggable
();
$model
->
name
=
$name
;
$model
->
save
();
$model
=
new
ActiveRecordSluggable
();
$model
->
sluggable
->
unique
=
true
;
$model
->
name
=
$name
;
$model
->
save
();
$this
->
assertEquals
(
'test-name-2'
,
$model
->
slug
);
}
/**
* @depends testUniqueByIncrement
*/
public
function
testUniqueByCallback
()
{
$name
=
'test name'
;
$model
=
new
ActiveRecordSluggable
();
$model
->
name
=
$name
;
$model
->
save
();
$model
=
new
ActiveRecordSluggable
();
$model
->
sluggable
->
unique
=
true
;
$model
->
sluggable
->
uniqueSlugGenerator
=
function
(
$baseSlug
,
$iteration
)
{
return
$baseSlug
.
'-callback'
;};
$model
->
name
=
$name
;
$model
->
save
();
$this
->
assertEquals
(
'test-name-callback'
,
$model
->
slug
);
}
/**
* @depends testUniqueByIncrement
*/
public
function
testUniqueByUniqueId
()
{
$name
=
'test name'
;
$model1
=
new
ActiveRecordSluggable
();
$model1
->
name
=
$name
;
$model1
->
save
();
$model2
=
new
ActiveRecordSluggable
();
$model2
->
sluggable
->
unique
=
true
;
$model2
->
sluggable
->
uniqueSlugGenerator
=
'uniqueid'
;
$model2
->
name
=
$name
;
$model2
->
save
();
$this
->
assertNotEquals
(
$model2
->
slug
,
$model1
->
slug
);
}
/**
* @depends testUniqueByIncrement
*/
public
function
testUniqueByTimestamp
()
{
$name
=
'test name'
;
$model1
=
new
ActiveRecordSluggable
();
$model1
->
name
=
$name
;
$model1
->
save
();
$model2
=
new
ActiveRecordSluggable
();
$model2
->
sluggable
->
unique
=
true
;
$model2
->
sluggable
->
uniqueSlugGenerator
=
'timestamp'
;
$model2
->
name
=
$name
;
$model2
->
save
();
$this
->
assertNotEquals
(
$model2
->
slug
,
$model1
->
slug
);
}
}
/**
* Test Active Record class with [[SluggableBehavior]] behavior attached.
*
* @property integer $id
* @property string $name
* @property string $slug
* @property integer $category_id
*
* @property SluggableBehavior $sluggable
*/
class
ActiveRecordSluggable
extends
ActiveRecord
{
public
function
behaviors
()
{
return
[
'sluggable'
=>
[
'class'
=>
SluggableBehavior
::
className
(),
'attribute'
=>
'name'
,
],
];
}
public
static
function
tableName
()
{
return
'test_slug'
;
}
/**
* @return SluggableBehavior
*/
public
function
getSluggable
()
{
return
$this
->
getBehavior
(
'sluggable'
);
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment