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
0f4d8ac4
Commit
0f4d8ac4
authored
Aug 12, 2014
by
Paul Klimov
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4675 from klimov-paul/4630-sluggable
Fix #4630: automatic generating of unique slug value to `yii\behaviors\Sluggable`
parents
accd2d31
c072cc2e
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
310 additions
and
4 deletions
+310
-4
CHANGELOG.md
framework/CHANGELOG.md
+2
-1
SluggableBehavior.php
framework/behaviors/SluggableBehavior.php
+114
-3
SluggableBehaviorTest.php
tests/unit/framework/behaviors/SluggableBehaviorTest.php
+194
-0
No files found.
framework/CHANGELOG.md
View file @
0f4d8ac4
...
...
@@ -176,8 +176,9 @@ Yii Framework 2 Change Log
-
Enh #4559: Added
`beforeValidateAll`
and
`afterValidateAll`
callbacks to
`ActiveForm`
(Alex-Code)
-
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 #4597:
`yii\composer\Installer::setPermission()`
supports setting permission for both directories and files now (qiangxue)
-
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 #4644: Added
`\yii\db\Schema::createColumnSchema()`
to be able to customize column schema used (mcd-php)
-
Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code)
-
Enh: Added support for using sub-queries when building a DB query with
`IN`
condition (qiangxue)
...
...
framework/behaviors/SluggableBehavior.php
View file @
0f4d8ac4
...
...
@@ -10,6 +10,8 @@ namespace yii\behaviors;
use
yii\base\InvalidConfigException
;
use
yii\db\BaseActiveRecord
;
use
yii\helpers\Inflector
;
use
yii\validators\UniqueValidator
;
use
Yii
;
/**
* SluggableBehavior automatically fills the specified attribute with a value that can be used a slug in a URL.
...
...
@@ -46,7 +48,9 @@ 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,7 +60,7 @@ 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
;
/**
...
...
@@ -72,6 +76,40 @@ 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
$ensureUnique
=
false
;
/**
* @var array configuration for slug uniqueness validator. Parameter 'class' may be omitted - by default
* [[UniqueValidator]] will be used.
* For example:
*
* ```php
* [
* 'filter' => ['type' => 1, 'status' => 2]
* ]
* ```
*
* @see UniqueValidator
*/
public
$uniqueValidator
=
[];
/**
* @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated
* slug is not unique. This should be a PHP callable with following signature:
*
* ```php
* function ($baseSlug, $iteration)
* {
* // return uniqueSlug
* }
* ```
*
* If not set unique slug will be generated adding incrementing suffix to the base slug.
*/
public
$uniqueSlugGenerator
;
/**
...
...
@@ -95,10 +133,83 @@ class SluggableBehavior extends AttributeBehavior
*/
protected
function
getValue
(
$event
)
{
$isNewSlug
=
true
;
if
(
$this
->
attribute
!==
null
)
{
$this
->
value
=
Inflector
::
slug
(
$this
->
owner
->
{
$this
->
attribute
});
$attributes
=
(
array
)
$this
->
attribute
;
/* @var $owner BaseActiveRecord */
$owner
=
$this
->
owner
;
if
(
!
$owner
->
getIsNewRecord
()
&&
!
empty
(
$owner
->
{
$this
->
slugAttribute
}))
{
$isNewSlug
=
false
;
foreach
(
$attributes
as
$attribute
)
{
if
(
$owner
->
isAttributeChanged
(
$attribute
))
{
$isNewSlug
=
true
;
break
;
}
}
}
if
(
$isNewSlug
)
{
$slugParts
=
[];
foreach
(
$attributes
as
$attribute
)
{
$slugParts
[]
=
$owner
->
{
$attribute
};
}
$slug
=
Inflector
::
slug
(
implode
(
'-'
,
$slugParts
));
}
else
{
$slug
=
$owner
->
{
$this
->
slugAttribute
};
}
}
else
{
$slug
=
parent
::
getValue
(
$event
);
}
if
(
$this
->
ensureUnique
&&
$isNewSlug
)
{
$baseSlug
=
$slug
;
$iteration
=
0
;
while
(
!
$this
->
validateSlug
(
$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
validateSlug
(
$slug
)
{
/* @var $validator UniqueValidator */
/* @var $model BaseActiveRecord */
$validator
=
Yii
::
createObject
(
array_merge
(
[
'class'
=>
UniqueValidator
::
className
()
],
$this
->
uniqueValidator
));
$model
=
clone
$this
->
owner
;
$model
->
clearErrors
();
$model
->
{
$this
->
slugAttribute
}
=
$slug
;
$validator
->
validateAttribute
(
$model
,
$this
->
slugAttribute
);
return
!
$model
->
hasErrors
();
}
return
parent
::
getValue
(
$event
);
/**
* Generates slug using configured callback or increment of iteration.
* @param string $baseSlug base slug value
* @param integer $iteration iteration number
* @return string new slug value
* @throws \yii\base\InvalidConfigException
*/
private
function
generateUniqueSlug
(
$baseSlug
,
$iteration
)
{
if
(
is_callable
(
$this
->
uniqueSlugGenerator
))
{
return
call_user_func
(
$this
->
uniqueSlugGenerator
,
$baseSlug
,
$iteration
);
}
else
{
return
$baseSlug
.
'-'
.
(
$iteration
+
1
);
}
}
}
tests/unit/framework/behaviors/SluggableBehaviorTest.php
0 → 100644
View file @
0f4d8ac4
<?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
ActiveRecordSluggableUnique
();
$model
->
name
=
$name
;
$model
->
save
();
$model
=
new
ActiveRecordSluggableUnique
();
$model
->
sluggable
->
uniqueSlugGenerator
=
'increment'
;
$model
->
name
=
$name
;
$model
->
save
();
$this
->
assertEquals
(
'test-name-2'
,
$model
->
slug
);
}
/**
* @depends testUniqueByIncrement
*/
public
function
testUniqueByCallback
()
{
$name
=
'test name'
;
$model
=
new
ActiveRecordSluggableUnique
();
$model
->
name
=
$name
;
$model
->
save
();
$model
=
new
ActiveRecordSluggableUnique
();
$model
->
sluggable
->
uniqueSlugGenerator
=
function
(
$baseSlug
,
$iteration
)
{
return
$baseSlug
.
'-callback'
;};
$model
->
name
=
$name
;
$model
->
save
();
$this
->
assertEquals
(
'test-name-callback'
,
$model
->
slug
);
}
/**
* @depends testSlug
*/
public
function
testUpdateUnique
()
{
$name
=
'test name'
;
$model
=
new
ActiveRecordSluggableUnique
();
$model
->
name
=
$name
;
$model
->
save
();
$model
->
save
();
$this
->
assertEquals
(
'test-name'
,
$model
->
slug
);
$model
=
ActiveRecordSluggableUnique
::
find
()
->
one
();
$model
->
save
();
$this
->
assertEquals
(
'test-name'
,
$model
->
slug
);
$model
->
name
=
'test-name'
;
$model
->
save
();
$this
->
assertEquals
(
'test-name'
,
$model
->
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'
);
}
}
class
ActiveRecordSluggableUnique
extends
ActiveRecordSluggable
{
public
function
behaviors
()
{
return
[
'sluggable'
=>
[
'class'
=>
SluggableBehavior
::
className
(),
'attribute'
=>
'name'
,
'ensureUnique'
=>
true
,
],
];
}
}
\ 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