Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slug generation from cyrillic strings #162

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ It also provides an easy entry point to use 3rd party libraries like the exellen
Sluggable generates slugs (uniqueness is not guaranteed) for an entity.
Will automatically generate on update/persist (you can disable the on update generation by overriding `getRegenerateSlugOnUpdate` to return false.
You can also override the slug delimiter from the default hyphen by overriding `getSlugDelimiter`.
Slug generation algo can be changed by overriding `generateSlugValue`.
Use cases include SEO (i.e. URLs like http://mysite.com/post/3/introduction-to-php)
```php
<?php
Expand All @@ -486,11 +487,16 @@ class BlogPost
* @ORM\Column(type="string")
*/
protected $title;

public function getSluggableFields()
{
return [ 'title' ];
}

public function generateSlugValue($values)
{
return implode('-', $values);
}
}
```

Expand Down Expand Up @@ -589,4 +595,4 @@ In the case of geocodable, you can set it as any service that implements `__invo

## Testing

[Read the documentation for testing ](doc/test.md)
[Read the documentation for testing ](doc/test.md)
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
],
"require": {
"php": ">=5.4",
"doctrine/common": ">=2.2"
"doctrine/common": ">=2.2",
"behat/transliterator": "~1.0"
},
"require-dev": {
"ext-pdo_sqlite": "*",
Expand Down
48 changes: 34 additions & 14 deletions src/Model/Sluggable/SluggableMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,45 @@ public function getSlug()
return $this->slug;
}

/**
* @param $values
* @return mixed|string
*/
private function generateSlugValue($values)
{
$usableValues = [];
foreach ($values as $fieldName => $fieldValue) {
if (!empty($fieldValue)) {
$usableValues[] = $fieldValue;
}
}

if (count($usableValues) < 1) {
throw new \UnexpectedValueException(
'Sluggable expects to have at least one usable (non-empty) field from the following: [ ' . implode(array_keys($values), ',') .' ]'
);
}

// generate the slug itself
$sluggableText = implode(' ', $usableValues);

$transliterator = new Transliterator;
$sluggableText = $transliterator->transliterate($sluggableText, $this->getSlugDelimiter());

$urlized = strtolower( trim( preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $sluggableText ), $this->getSlugDelimiter() ) );
$urlized = preg_replace("/[\/_|+ -]+/", $this->getSlugDelimiter(), $urlized);

return $urlized;
}

/**
* Generates and sets the entity's slug. Called prePersist and preUpdate
*/
public function generateSlug()
{
if ( $this->getRegenerateSlugOnUpdate() || empty( $this->slug ) ) {
$fields = $this->getSluggableFields();
$usableValues = [];
$values = [];

foreach ($fields as $field) {
if (property_exists($this, $field)) {
Expand All @@ -85,21 +116,10 @@ public function generateSlug()
}
}

if ( !empty( $val ) ) {
$usableValues[] = $val;
}
}

if ( count($usableValues) < 1 ) {
throw new \UnexpectedValueException('Sluggable expects to have at least one usable (non-empty) field from the following: [ ' . implode($fields, ',') .' ]');
$values[] = $val;
}

// generate the slug itself
$sluggableText = implode($usableValues, ' ');
$urlized = strtolower( trim( preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', iconv('UTF-8', 'ASCII//TRANSLIT', $sluggableText) ), $this->getSlugDelimiter() ) );
$urlized = preg_replace("/[\/_|+ -]+/", $this->getSlugDelimiter(), $urlized);

$this->slug = $urlized;
$this->slug = $this->generateSlugValue($values);
}
}
}
15 changes: 15 additions & 0 deletions src/Model/Sluggable/Transliterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* @author Lusitanian
* Freely released with no restrictions, re-license however you'd like!
*/

namespace Knp\DoctrineBehaviors\Model\Sluggable;

/**
* Transliteration utility
*/
class Transliterator extends \Behat\Transliterator\Transliterator
{

}
8 changes: 4 additions & 4 deletions tests/Knp/DoctrineBehaviors/ORM/SluggableMultiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function testSlugLoading()

$entity = new \BehaviorFixtures\ORM\SluggableMultiEntity();

$expected = 'the-name-title';
$expected = 'the+name+title';

$entity->setName('The name');

Expand All @@ -61,7 +61,7 @@ public function testNotUpdatedSlug()

$entity = new \BehaviorFixtures\ORM\SluggableMultiEntity();

$expected = 'the-name-title';
$expected = 'the+name+title';

$entity->setName('The name');

Expand All @@ -82,7 +82,7 @@ public function testUpdatedSlug()

$entity = new \BehaviorFixtures\ORM\SluggableMultiEntity();

$expected = 'the-name-title';
$expected = 'the+name+title';

$entity->setName('The name');

Expand All @@ -91,7 +91,7 @@ public function testUpdatedSlug()

$this->assertEquals($entity->getSlug(), $expected);

$expected = 'the-name-2-title';
$expected = 'the+name+2+title';

$entity->setName('The name 2');

Expand Down
61 changes: 42 additions & 19 deletions tests/Knp/DoctrineBehaviors/ORM/SluggableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class SluggableTest extends \PHPUnit_Framework_TestCase

protected function getUsedEntityFixtures()
{
return array(
return [
'BehaviorFixtures\\ORM\\SluggableEntity'
);
];
}

protected function getEventManager()
Expand Down Expand Up @@ -52,28 +52,51 @@ public function testSlugLoading()
$entity = $em->getRepository('BehaviorFixtures\ORM\SluggableEntity')->find($id);

$this->assertNotNull($entity);
$this->assertEquals($entity->getSlug(), $expected);
$this->assertEquals($expected, $entity->getSlug());
}

public function testNotUpdatedSlug()
{
$em = $this->getEntityManager();

$entity = new \BehaviorFixtures\ORM\SluggableEntity();

$expected = 'the-name';

$entity->setName('The name');

$em->persist($entity);
$em->flush();

$entity->setDate(new \DateTime);

$em->persist($entity);
$em->flush();

$this->assertEquals($entity->getSlug(), $expected);
$data = [
[
'slug' => 'the-name',
'name' => 'The name',
],
[
'slug' => 'loic-rene',
'name' => 'Löic & René',
],
[
'slug' => 'ivan-ivanovich',
'name' => 'Иван Иванович',
],
[
'slug' => 'chateauneuf-du-pape',
'name' => 'Châteauneuf du Pape'
],
[
'slug' => 'zlutoucky-kun',
'name' => 'Žluťoučký kůň'
]
];

foreach ($data as $row) {
$entity = new \BehaviorFixtures\ORM\SluggableEntity();

$entity->setName($row['name']);

$em->persist($entity);
$em->flush();

$entity->setDate(new \DateTime);

$em->persist($entity);
$em->flush();

$this->assertEquals($row['slug'], $entity->getSlug());
}
}

public function testUpdatedSlug()
Expand All @@ -98,6 +121,6 @@ public function testUpdatedSlug()
$em->persist($entity);
$em->flush();

$this->assertEquals($entity->getSlug(), $expected);
$this->assertEquals($expected, $entity->getSlug());
}
}
13 changes: 12 additions & 1 deletion tests/fixtures/BehaviorFixtures/ORM/SluggableMultiEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function setDate($date)
return $this;
}

protected function getSluggableFields()
public function getSluggableFields()
{
return [ 'name', 'title' ];
}
Expand All @@ -77,4 +77,15 @@ public function getTitle()
{
return 'title';
}

/**
* @param $values
* @return mixed|string
*/
public function generateSlugValue($values)
{
$sluggableText = implode(' ', $values);

return strtolower(str_replace(' ', '+', $sluggableText));
}
}