diff --git a/.travis.yml b/.travis.yml index cfd3e12..fa80f1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ env: global: - DEFAULT=1 +services: + - postgresql + - mysql + matrix: fast_finish: true diff --git a/src/Model/Behavior/SlugBehavior.php b/src/Model/Behavior/SlugBehavior.php index 060629c..f400917 100644 --- a/src/Model/Behavior/SlugBehavior.php +++ b/src/Model/Behavior/SlugBehavior.php @@ -12,6 +12,7 @@ use Cake\Validation\Validator; use InvalidArgumentException; use Muffin\Slug\SluggerInterface; +use RuntimeException; /** * Slug behavior. @@ -34,6 +35,8 @@ class SlugBehavior extends Behavior * to `Muffin\Slug\Slugger\CakeSlugger`. * - unique: Tells if slugs should be unique. Set this to a callable if you * want to customize how unique slugs are generated. Defaults to `true`. + * - virtual: Tells if slugs are a virtual property of the entity or not to skip + * validating the column's existence and length. * - scope: Extra conditions or a callable `$callable($entity)` used when * checking a slug for uniqueness. * - implementedEvents: Events this behavior listens to. Defaults to @@ -64,6 +67,7 @@ class SlugBehavior extends Behavior 'maxLength' => null, 'slugger' => 'Muffin\Slug\Slugger\CakeSlugger', 'unique' => true, + 'virtual' => false, 'scope' => [], 'implementedEvents' => [ 'Model.buildValidator' => 'buildValidator', @@ -113,16 +117,40 @@ public function initialize(array $config) $this->setConfig('displayField', $this->_table->getDisplayField()); } + if ($this->getConfig('unique') === true) { + $this->setConfig('unique', [$this, '_uniqueSlug']); + } + + if ($this->getConfig('virtual')) { + return; + } + + $field = $this->getConfig('field'); + + if (!$this->getTable()->hasField($field)) { + throw new RuntimeException(sprintf( + 'SlugBehavior: Table `%s` is missing field `%s`', + $this->getTable()->getTable(), + $field + )); + } + + $fieldSchema = $this->_table->getSchema()->getColumn($field); + if ($this->getConfig('maxLength') === null) { + if ($fieldSchema['length'] === null) { + throw new RuntimeException(sprintf( + 'SlugBehavior: The schema for field `%s.%s` has no length defined', + $this->getTable()->getTable(), + $field + )); + } + $this->setConfig( 'maxLength', - $this->_table->getSchema()->getColumn($this->getConfig('field'))['length'] + $fieldSchema['length'] ); } - - if ($this->getConfig('unique') === true) { - $this->setConfig('unique', [$this, '_uniqueSlug']); - } } /** @@ -204,7 +232,8 @@ public function beforeSave(Event $event, Entity $entity, ArrayObject $options) $onDirty = $this->getConfig('onDirty'); $field = $this->getConfig('field'); - if (!$onDirty + if ( + !$onDirty && $entity->isDirty($field) && (!$entity->isNew() || (!empty($entity->{$field}))) ) { diff --git a/src/Slugger/CakeSlugger.php b/src/Slugger/CakeSlugger.php index cb69ae5..73e4545 100644 --- a/src/Slugger/CakeSlugger.php +++ b/src/Slugger/CakeSlugger.php @@ -20,7 +20,7 @@ class CakeSlugger implements SluggerInterface * @var array */ public $config = [ - 'lowercase' => true + 'lowercase' => true, ]; /** diff --git a/src/Slugger/CocurSlugger.php b/src/Slugger/CocurSlugger.php index 4c37147..c7f54b1 100644 --- a/src/Slugger/CocurSlugger.php +++ b/src/Slugger/CocurSlugger.php @@ -21,7 +21,7 @@ class CocurSlugger implements SluggerInterface */ public $config = [ 'regex' => null, - 'lowercase' => true + 'lowercase' => true, ]; /** diff --git a/tests/Fixture/ArticlesFixture.php b/tests/Fixture/ArticlesFixture.php index 0f1529b..55ea5e0 100644 --- a/tests/Fixture/ArticlesFixture.php +++ b/tests/Fixture/ArticlesFixture.php @@ -16,11 +16,11 @@ class ArticlesFixture extends TestFixture 'id' => ['type' => 'integer'], 'author_id' => ['type' => 'integer'], 'title' => ['type' => 'string', 'null' => false], - 'sub_title' => ['type' => 'string', 'null' => false], - 'slug' => ['type' => 'string', 'null' => false], + 'sub_title' => ['type' => 'string', 'null' => false, 'length' => 255], + 'slug' => ['type' => 'string', 'null' => false, 'length' => 255], 'created' => ['type' => 'datetime', 'null' => true], 'modified' => ['type' => 'datetime', 'null' => true], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] + '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], ]; /** diff --git a/tests/Fixture/ArticlesTagsFixture.php b/tests/Fixture/ArticlesTagsFixture.php index e727e20..e13f6a6 100644 --- a/tests/Fixture/ArticlesTagsFixture.php +++ b/tests/Fixture/ArticlesTagsFixture.php @@ -11,7 +11,7 @@ class ArticlesTagsFixture extends TestFixture public $fields = [ 'id' => ['type' => 'integer'], 'article_id' => ['type' => 'integer'], - 'slug_tag_id' => ['type' => 'integer'] + 'slug_tag_id' => ['type' => 'integer'], ]; public $records = [ diff --git a/tests/Fixture/AuthorsFixture.php b/tests/Fixture/AuthorsFixture.php index 3c308c2..e0773eb 100644 --- a/tests/Fixture/AuthorsFixture.php +++ b/tests/Fixture/AuthorsFixture.php @@ -15,7 +15,7 @@ class AuthorsFixture extends TestFixture public $fields = [ 'id' => ['type' => 'integer'], 'name' => ['type' => 'string', 'null' => false], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]] + '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], ]; /** diff --git a/tests/Fixture/RecipesFixture.php b/tests/Fixture/RecipesFixture.php new file mode 100644 index 0000000..2a6ee08 --- /dev/null +++ b/tests/Fixture/RecipesFixture.php @@ -0,0 +1,29 @@ + ['type' => 'integer'], + 'slug' => ['type' => 'string'], + 'name' => ['type' => 'string', 'length' => 320], + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ], + ]; + + public $records = [ + [ + 'slug' => 'cake', + 'name' => 'Cake', + ], + [ + 'slug' => 'muffin', + 'name' => 'Muffin', + ], + ]; +} diff --git a/tests/TestCase/Model/Behavior/SlugBehaviorTest.php b/tests/TestCase/Model/Behavior/SlugBehaviorTest.php index f7ec020..17d163e 100644 --- a/tests/TestCase/Model/Behavior/SlugBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/SlugBehaviorTest.php @@ -12,6 +12,7 @@ class SlugBehaviorTest extends TestCase 'plugin.Muffin/Slug.Articles', 'plugin.Muffin/Slug.ArticlesTags', 'plugin.Muffin/Slug.Authors', + 'plugin.Muffin/Slug.Recipes', ]; public function setUp() @@ -440,4 +441,31 @@ public function testCallableForUnique() $this->assertEquals('color', $this->Tags->slug($newEntity)); } + + public function testSlugMissingFieldLengthThrowsException() + { + $table = TableRegistry::get('Muffin/Slug.Recipes', ['table' => 'slug_recipes']); + + $this->skipIf(get_class($table->getConnection()->getDriver()) !== 'postgres'); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('SlugBehavior: The schema for field `slug_recipes.slug` has no length defined'); + + $table->addBehavior('Muffin/Slug.Slug'); + } + + public function testSlugMissingFieldThrowsException() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('SlugBehavior: Table `slug_recipes` is missing field `real_slug`'); + $table = TableRegistry::get('Muffin/Slug.Recipes', ['table' => 'slug_recipes']); + $table->addBehavior('Muffin/Slug.Slug', ['field' => 'real_slug']); + } + + public function testVirtualSlugDoesNotThrowExceptions() + { + $table = TableRegistry::get('Muffin/Slug.Recipes', ['table' => 'slug_recipes']); + $table->addBehavior('Muffin/Slug.Slug', ['field' => 'real_slug', 'virtual' => true]); + $this->assertTrue($table->hasBehavior('Slug')); + } }