From ab10dd03420d4fa4fb71321b083722ac4f2fd1f2 Mon Sep 17 00:00:00 2001 From: "Christopher C. Wells" Date: Fri, 1 Jan 2021 20:34:55 -0800 Subject: [PATCH] Use slugs for recipe and food routes --- app/Models/Food.php | 7 +- app/Models/JournalEntry.php | 2 +- app/Models/Recipe.php | 7 +- .../Journalable.php} | 6 +- app/Models/Traits/Sluggable.php | 36 +++++ composer.json | 1 + composer.lock | 143 +++++++++++++++++- config/sluggable.php | 141 +++++++++++++++++ .../2020_12_21_214128_create_foods_table.php | 1 + ...2020_12_21_215932_create_recipes_table.php | 1 + 10 files changed, 338 insertions(+), 7 deletions(-) rename app/Models/{JournalableModel.php => Traits/Journalable.php} (71%) create mode 100644 app/Models/Traits/Sluggable.php create mode 100644 config/sluggable.php diff --git a/app/Models/Food.php b/app/Models/Food.php index 1f5f606..95495bf 100644 --- a/app/Models/Food.php +++ b/app/Models/Food.php @@ -2,7 +2,10 @@ namespace App\Models; +use App\Models\Traits\Journalable; +use App\Models\Traits\Sluggable; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; /** * @property int id @@ -21,9 +24,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; * @property \Illuminate\Support\Carbon created_at * @property \Illuminate\Support\Carbon updated_at */ -class Food extends JournalableModel +class Food extends Model { use HasFactory; + use Sluggable; + use Journalable; /** * @inheritdoc diff --git a/app/Models/JournalEntry.php b/app/Models/JournalEntry.php index cf2fcb4..1839f26 100644 --- a/app/Models/JournalEntry.php +++ b/app/Models/JournalEntry.php @@ -54,7 +54,7 @@ class JournalEntry extends Model /** * @inheritdoc */ - protected $with = ['user', 'foods:id,name', 'recipes:id,name']; + protected $with = ['user', 'foods:id,name,slug', 'recipes:id,name,slug']; /** * Get the User this entry belongs to. diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index f003a5b..fb2ec05 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -2,7 +2,10 @@ namespace App\Models; +use App\Models\Traits\Journalable; +use App\Models\Traits\Sluggable; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; /** @@ -27,9 +30,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @method float sodiumTotal Get total sodium. * @method float sodiumPerServing Get per serving sodium. */ -class Recipe extends JournalableModel +class Recipe extends Model { use HasFactory; + use Journalable; + use Sluggable; /** * @inheritdoc diff --git a/app/Models/JournalableModel.php b/app/Models/Traits/Journalable.php similarity index 71% rename from app/Models/JournalableModel.php rename to app/Models/Traits/Journalable.php index ffd6b2f..fdb9f57 100644 --- a/app/Models/JournalableModel.php +++ b/app/Models/Traits/Journalable.php @@ -1,11 +1,11 @@ ['source' => 'name']]; + } + + /** + * @inheritdoc + */ + public function getRouteKeyName(): string + { + return $this->getSlugKeyName(); + } + + /** + * @inheritdoc + */ + public function getRouteKey(): string + { + return $this->getSlugKey(); + } +} diff --git a/composer.json b/composer.json index aa400d8..fd6b85b 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "license": "MPL-2.0", "require": { "php": "^8.0", + "cviebrock/eloquent-sluggable": "^8.0", "fideloper/proxy": "^4.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", diff --git a/composer.lock b/composer.lock index 3790647..527b4bd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d33692a22f60e4e61e7aa7195979cdfd", + "content-hash": "15b3829c7b58db14299bd2c323539f47", "packages": [ { "name": "asm89/stack-cors", @@ -118,6 +118,147 @@ ], "time": "2020-08-18T23:57:15+00:00" }, + { + "name": "cocur/slugify", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cocur/slugify.git", + "reference": "3f1ffc300f164f23abe8b64ffb3f92d35cec8307" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cocur/slugify/zipball/3f1ffc300f164f23abe8b64ffb3f92d35cec8307", + "reference": "3f1ffc300f164f23abe8b64ffb3f92d35cec8307", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=7.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.1", + "latte/latte": "~2.2", + "league/container": "^2.2.0", + "mikey179/vfsstream": "~1.6.8", + "mockery/mockery": "^1.3", + "nette/di": "~2.4", + "phpunit/phpunit": "^5.7.27", + "pimple/pimple": "~1.1", + "plumphp/plum": "~0.1", + "symfony/config": "^3.4 || ^4.3 || ^5.0", + "symfony/dependency-injection": "^3.4 || ^4.3 || ^5.0", + "symfony/http-kernel": "^3.4 || ^4.3 || ^5.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/master" + }, + "time": "2019-12-14T13:04:14+00:00" + }, + { + "name": "cviebrock/eloquent-sluggable", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/cviebrock/eloquent-sluggable.git", + "reference": "1b693b333de9080380340facf3806c644a949ad7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cviebrock/eloquent-sluggable/zipball/1b693b333de9080380340facf3806c644a949ad7", + "reference": "1b693b333de9080380340facf3806c644a949ad7", + "shasum": "" + }, + "require": { + "cocur/slugify": "^4.0", + "illuminate/config": "^8.0", + "illuminate/database": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "limedeck/phpunit-detailed-printer": "^6.0", + "mockery/mockery": "^1.4.2", + "orchestra/database": "^6.0", + "orchestra/testbench": "^6.0", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Cviebrock\\EloquentSluggable\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Cviebrock\\EloquentSluggable\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin Viebrock", + "email": "colin@viebrock.ca" + } + ], + "description": "Easy creation of slugs for your Eloquent models in Laravel", + "homepage": "https://github.com/cviebrock/eloquent-sluggable", + "keywords": [ + "eloquent", + "eloquent-sluggable", + "laravel", + "lumen", + "slug", + "sluggable" + ], + "support": { + "issues": "https://github.com/cviebrock/eloquent-sluggable/issues", + "source": "https://github.com/cviebrock/eloquent-sluggable/tree/8.0.2" + }, + "time": "2020-11-29T18:53:58+00:00" + }, { "name": "dnoegel/php-xdg-base-dir", "version": "v0.1.1", diff --git a/config/sluggable.php b/config/sluggable.php new file mode 100644 index 0000000..6d3223d --- /dev/null +++ b/config/sluggable.php @@ -0,0 +1,141 @@ +name; + * + * Or it can be an array of fields, like ["name", "company"], which builds a slug from: + * + * $model->name . ' ' . $model->company; + * + * If you've defined custom getters in your model, you can use those too, + * since Eloquent will call them when you request a custom attribute. + * + * Defaults to null, which uses the toString() method on your model. + */ + + 'source' => null, + + /** + * The maximum length of a generated slug. Defaults to "null", which means + * no length restrictions are enforced. Set it to a positive integer if you + * want to make sure your slugs aren't too long. + */ + + 'maxLength' => null, + + /** + * If you are setting a maximum length on your slugs, you may not want the + * truncated string to split a word in half. The default setting of "true" + * will ensure this, e.g. with a maxLength of 12: + * + * "my source string" -> "my-source" + * + * Setting it to "false" will simply truncate the generated slug at the + * desired length, e.g.: + * + * "my source string" -> "my-source-st" + */ + + 'maxLengthKeepWords' => true, + + /** + * If left to "null", then use the cocur/slugify package to generate the slug + * (with the separator defined below). + * + * Set this to a closure that accepts two parameters (string and separator) + * to define a custom slugger. e.g.: + * + * 'method' => function( $string, $sep ) { + * return preg_replace('/[^a-z]+/i', $sep, $string); + * }, + * + * Otherwise, this will be treated as a callable to be used. e.g.: + * + * 'method' => array('Str','slug'), + */ + + 'method' => null, + + /** + * Separator to use when generating slugs. Defaults to a hyphen. + */ + + 'separator' => '-', + + /** + * Enforce uniqueness of slugs? Defaults to true. + * If a generated slug already exists, an incremental numeric + * value will be appended to the end until a unique slug is found. e.g.: + * + * my-slug + * my-slug-1 + * my-slug-2 + */ + + 'unique' => true, + + /** + * If you are enforcing unique slugs, the default is to add an + * incremental value to the end of the base slug. Alternatively, you + * can change this value to a closure that accepts three parameters: + * the base slug, the separator, and a Collection of the other + * "similar" slugs. The closure should return the new unique + * suffix to append to the slug. + */ + + 'uniqueSuffix' => null, + + /** + * Should we include the trashed items when generating a unique slug? + * This only applies if the softDelete property is set for the Eloquent model. + * If set to "false", then a new slug could duplicate one that exists on a trashed model. + * If set to "true", then uniqueness is enforced across trashed and existing models. + */ + + 'includeTrashed' => false, + + /** + * An array of slug names that can never be used for this model, + * e.g. to prevent collisions with existing routes or controller methods, etc.. + * Defaults to null (i.e. no reserved names). + * Can be a static array, e.g.: + * + * 'reserved' => array('add', 'delete'), + * + * or a closure that returns an array of reserved names. + * If using a closure, it will accept one parameter: the model itself, and should + * return an array of reserved names, or null. e.g. + * + * 'reserved' => function( Model $model) { + * return $model->some_method_that_returns_an_array(); + * } + * + * In the case of a slug that gets generated with one of these reserved names, + * we will do: + * + * $slug .= $separator + "1" + * + * and continue from there. + */ + + 'reserved' => null, + + /** + * Whether to update the slug value when a model is being + * re-saved (i.e. already exists). Defaults to false, which + * means slugs are not updated. + * + * Be careful! If you are using slugs to generate URLs, then + * updating your slug automatically might change your URLs which + * is probably not a good idea from an SEO point of view. + * Only set this to true if you understand the possible consequences. + */ + + 'onUpdate' => false, + +]; diff --git a/database/migrations/2020_12_21_214128_create_foods_table.php b/database/migrations/2020_12_21_214128_create_foods_table.php index dfab13d..3f15e09 100644 --- a/database/migrations/2020_12_21_214128_create_foods_table.php +++ b/database/migrations/2020_12_21_214128_create_foods_table.php @@ -14,6 +14,7 @@ class CreateFoodsTable extends Migration Schema::create('foods', function (Blueprint $table) { $table->id(); $table->string('name'); + $table->string('slug')->unique(); $table->string('detail')->nullable(); $table->string('brand')->nullable(); $table->unsignedFloat('serving_size'); diff --git a/database/migrations/2020_12_21_215932_create_recipes_table.php b/database/migrations/2020_12_21_215932_create_recipes_table.php index 349209b..ee85613 100644 --- a/database/migrations/2020_12_21_215932_create_recipes_table.php +++ b/database/migrations/2020_12_21_215932_create_recipes_table.php @@ -16,6 +16,7 @@ class CreateRecipesTable extends Migration Schema::create('recipes', function (Blueprint $table) { $table->id(); $table->string('name'); + $table->string('slug')->unique(); $table->longText('description'); $table->unsignedInteger('servings'); $table->timestamps();