diff --git a/composer.json b/composer.json index f9868fe..609119f 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "require": { "vlucas/phpdotenv": "^5.5", "symfony/console": "^5.4", - "phpmailer/phpmailer": "^6.8" + "phpmailer/phpmailer": "^6.8", + "cocur/slugify": "^4.3" }, "require-dev": { "phpunit/phpunit": "^9.6", diff --git a/composer.lock b/composer.lock index 7107952..890f60e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,82 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "079e8a8443fdc25cbe3b03322494b73b", + "content-hash": "d873b245eaaf660d70c21e06ee4bcf0a", "packages": [ + { + "name": "cocur/slugify", + "version": "v4.3.0", + "source": { + "type": "git", + "url": "https://github.com/cocur/slugify.git", + "reference": "652234ef5f1be844a2ae1c36ad1b4c88b05160f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cocur/slugify/zipball/652234ef5f1be844a2ae1c36ad1b4c88b05160f9", + "reference": "652234ef5f1be844a2ae1c36ad1b4c88b05160f9", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "conflict": { + "symfony/config": "<3.4 || >=4,<4.3", + "symfony/dependency-injection": "<3.4 || >=4,<4.3", + "symfony/http-kernel": "<3.4 || >=4,<4.3", + "twig/twig": "<2.12.1" + }, + "require-dev": { + "laravel/framework": "^5.0|^6.0|^7.0|^8.0", + "latte/latte": "~2.2", + "league/container": "^2.2.0", + "mikey179/vfsstream": "~1.6.8", + "mockery/mockery": "^1.3", + "nette/di": "~2.4", + "pimple/pimple": "~1.1", + "plumphp/plum": "~0.1", + "symfony/config": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/dependency-injection": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/http-kernel": "^3.4 || ^4.3 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0", + "twig/twig": "^2.12.1 || ~3.0", + "zendframework/zend-modulemanager": "~2.2", + "zendframework/zend-servicemanager": "~2.2", + "zendframework/zend-view": "~2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cocur\\Slugify\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Eckerstorfer", + "email": "florian@eckerstorfer.co", + "homepage": "https://florian.ec" + }, + { + "name": "Ivo Bathke", + "email": "ivo.bathke@gmail.com" + } + ], + "description": "Converts a string into a slug.", + "keywords": [ + "slug", + "slugify" + ], + "support": { + "issues": "https://github.com/cocur/slugify/issues", + "source": "https://github.com/cocur/slugify/tree/v4.3.0" + }, + "time": "2022-12-07T19:48:48+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.1", diff --git a/src/Interfaces/PersistentData/IPersistentDataManager.php b/src/Interfaces/PersistentData/IPersistentDataManager.php index 7978fc7..6cdf496 100644 --- a/src/Interfaces/PersistentData/IPersistentDataManager.php +++ b/src/Interfaces/PersistentData/IPersistentDataManager.php @@ -12,6 +12,8 @@ interface IPersistentDataManager public function selectFromDbById($id, string $type, bool $useRelations = false, array $withRelations = []); + public function selectFromDbBySlug(string $slug, string $type, bool $useRelations = false, array $withRelations = []); + public function loadRelationsFromDb(Model $model, bool $recursive = false, array $withRelations = []): void; public function saveToDb(Model $model): void; diff --git a/src/PersistentData/Model/Model.php b/src/PersistentData/Model/Model.php index 23b2840..c5f566c 100644 --- a/src/PersistentData/Model/Model.php +++ b/src/PersistentData/Model/Model.php @@ -41,7 +41,7 @@ abstract class Model { $array = []; - foreach (self::getFields() as $key) { + foreach (static::getFields() as $key) { $method = 'get' . str_replace('_', '', ucwords($key, '_')); if (method_exists($this, $method)) { diff --git a/src/PersistentData/Model/ModelWithSlug.php b/src/PersistentData/Model/ModelWithSlug.php new file mode 100644 index 0000000..036bedb --- /dev/null +++ b/src/PersistentData/Model/ModelWithSlug.php @@ -0,0 +1,32 @@ +slug = $slug; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function generateSlug(): string + { + $slugSourceGetMethod = 'get' . str_replace('_', '', ucwords(static::$slugSource, '_')); + $this->slug = Slugify::create()->slugify($this->$slugSourceGetMethod()); + return $this->slug; + } +} diff --git a/src/PersistentData/PersistentDataManager.php b/src/PersistentData/PersistentDataManager.php index 22c9fee..e3096d2 100644 --- a/src/PersistentData/PersistentDataManager.php +++ b/src/PersistentData/PersistentDataManager.php @@ -8,9 +8,12 @@ use SokoWeb\Interfaces\Database\IAuditLogger; use SokoWeb\Interfaces\Database\IResultSet; use SokoWeb\Interfaces\PersistentData\IPersistentDataManager; use SokoWeb\PersistentData\Model\Model; +use SokoWeb\PersistentData\Model\ModelWithSlug; class PersistentDataManager implements IPersistentDataManager { + const SLUG_MAX_LENGTH = 255; + private IConnection $dbConnection; private ?IAuditLogger $auditLogger; @@ -58,6 +61,14 @@ class PersistentDataManager implements IPersistentDataManager return $this->selectFromDb($select, $type, $useRelations, $withRelations); } + public function selectFromDbBySlug(string $slug, string $type, bool $useRelations = false, array $withRelations = []) + { + $select = new Select($this->dbConnection); + $select->where('slug', '=', $slug); + + return $this->selectFromDb($select, $type, $useRelations, $withRelations); + } + public function loadRelationsFromDb(Model $model, bool $recursive = false, array $withRelations = []): void { $relations = $model::getRelations(); @@ -103,12 +114,21 @@ class PersistentDataManager implements IPersistentDataManager } if (count($modified) > 0) { + if ($model instanceof ModelWithSlug && isset($modified['slug'])) { + $modified['slug'] = $this->generateUniqueSlug($model, $modified['slug']); + } + $modify->setId($id); $modify->setDiff($diff); $modify->fill($modified); $modify->save(); } } else { + if ($model instanceof ModelWithSlug) { + $slug = $model->generateSlug(); + $modified['slug'] = $this->generateUniqueSlug($model, $slug); + } + $modify->fill($modified); $modify->save(); @@ -129,6 +149,29 @@ class PersistentDataManager implements IPersistentDataManager $model->resetSnapshot(); } + private function generateUniqueSlug(ModelWithSlug $model, string $notUniqueSlug): string + { + $numbered = 1; + + do { + if ($numbered > 1) { + $slug = substr($notUniqueSlug, 0, static::SLUG_MAX_LENGTH - (strlen((string)$numbered) + 1)); + $slug = $notUniqueSlug . '_' . (string)$numbered; + } else { + $slug = substr($notUniqueSlug, 0, static::SLUG_MAX_LENGTH); + } + + $select = new Select($this->dbConnection, $model::getTable()); + $select->where('slug', '=', $slug); + + $numbered++; + } while ($select->count() != 0); + + $model->setSlug($slug); + + return $slug; + } + private function createSelect(Select $select, string $type, bool $useRelations = false, array $withRelations = []): Select { $table = call_user_func([$type, 'getTable']);