tobento / app-slugging
App slugging support.
Requires
- php: >=8.0
- psr/container: ^2.0
- tobento/app: ^1.0.7
- tobento/app-database: ^1.0
- tobento/app-migration: ^1.0
- tobento/service-autowire: ^1.0.9
- tobento/service-repository: ^1.0
- tobento/service-repository-storage: ^1.0
- tobento/service-slugifier: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- tobento/app-http: ^1.0 || ^1.1
- tobento/app-testing: ^1.0
- tobento/service-container: ^1.0
- tobento/service-validation: ^1.0
- vimeo/psalm: ^4.0
README
Slugging support for the app using the Slugifier Service.
Table of Contents
Getting Started
Add the latest version of the app slugging project running this command.
composer require tobento/app-slugging
Requirements
- PHP 8.0 or greater
Documentation
App
Check out the App Skeleton if you are using the skeleton.
You may also check out the App to learn more about the app in general.
Slugging Boot
The slugging boot does the following:
- installs and loads slugging config file
- implements slugifier interfaces
use Tobento\App\AppFactory; use Tobento\App\Slugging\SlugRepositoryInterface; use Tobento\Service\Slugifier\SlugifierFactoryInterface; use Tobento\Service\Slugifier\SlugifierInterface; use Tobento\Service\Slugifier\SlugifiersInterface; use Tobento\Service\Slugifier\SlugsInterface; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots $app->boot(\Tobento\App\Slugging\Boot\Slugging::class); $app->booting(); // Implemented interfaces: $slugifierFactory = $app->get(SlugifierFactoryInterface::class); $slugifier = $app->get(SlugifierInterface::class); $slugifiers = $app->get(SlugifiersInterface::class); $slugs = $app->get(SlugsInterface::class); $slugRepository = $app->get(SlugRepositoryInterface::class); // Run the app $app->run();
Slugging Config
The configuration for the slugging is located in the app/config/slugging.php
file at the default App Skeleton config location where you can specify the slugifiers for your application and more.
Generating Slugs
To generate slugs use the slugifier interfaces:
use Tobento\Service\Slugifier\SlugifierInterface; use Tobento\Service\Slugifier\SlugifiersInterface; class SomeService { public function __construct( protected SlugifierInterface $slugifier, protected SlugifiersInterface $slugifiers, ) {} private function slugify() { // using the default slugifier: $slug = $this->slugifier->slugify(string: 'Lorem Ipsum!', locale: 'de'); // using a custom slugifier: $slug = $this->slugifiers->get('custom')->slugify('Lorem Ipsum!'); } }
You may check out the Slugifier Service to learn more about it.
Adding Slugs
You may add slugs to prevent dublicate slugs or for routing purposes such as using the Slug Matches on routes.
From Config
You may add slugs using resources directly in the Slugging Config.
Using The App
Sometimes, it may be useful to add slugs using resources within the app:
use Tobento\Service\Slugifier\Resource\ArrayResource; use Tobento\Service\Slugifier\SlugsInterface; // Adding slugs resources only if requested: $app->on(SlugsInterface::class, static function(SlugsInterface $slugs): void { $slugs->addResource(new ArrayResource( slugs: ['login'], )); });
Repository Resource
With the RepositoryResource
class you can add any repository implementing the RepositoryInterface
as a resource.
use Tobento\App\Slugging\Resource\RepositoryResource; use Tobento\Service\Repository\RepositoryInterface; use Tobento\Service\Slugifier\SlugsInterface; // Adding slugs resources only if requested: $app->on(SlugsInterface::class, static function(SlugsInterface $slugs, BlogRepositoryInterface $blogRepo): void { $slugs->addResource(new RepositoryResource( repository: $blogRepo, priority: 100, // higher priority will be first. resourceKey: 'blog', // or null // or using a closure: resourceKey: static function (null|object $blog): null|string { return $blog->resourceKey(); }, resourceId: static function (object $blog): null|string|int { return $blog->id(); }, // or null if none: resourceId: null, // you may customize the where query parameters: whereParameters: static function (string $slug, string $locale): array { return $locale === '' ? ['slug' => $slug] // locale independent (default) : ['slug' => $slug, 'locale' => $locale]; // locale dependent // JSON SYNTAX: return ['slug->'.$locale => $slug]; // locale dependent }, )); });
Slug Repository
By default, the slug repository is added to the slugs in the Slugging Config whereby preventing dublicated slugs.
The advantage using the slug repository is that there will be just one query while generating slugs or when using the Slug Matches if it is the only added slug resource.
Saving Slugs
Use the saveSlug
method to save a slug:
use Tobento\App\Slugging\SlugRepositoryInterface; use Tobento\Service\Slugifier\Slug; $slugRepository = $app->get(SlugRepositoryInterface::class); $savedSlug = $slugRepository->saveSlug(new Slug( slug: 'lorem-ipsum', locale: 'en', resourceKey: 'blog', // null|string resourceId: 125, // null|int|string ));
Deleting Slugs
Use the deleteSlug
method to delete a slug:
use Tobento\App\Slugging\SlugRepositoryInterface; use Tobento\Service\Slugifier\Slug; $slugRepository = $app->get(SlugRepositoryInterface::class); $deletedSlug = $slugRepository->deleteSlug(new Slug( slug: 'lorem-ipsum', locale: 'en', ));
Routing
First, you will need to install the App Http.
Slug Matches
You may use the SlugMatches
class to have mutliple routes with a slug only uri matching different controllers based on the resouceKey
parameter.
use Tobento\App\Slugging\Routing\SlugMatches; $app->route( method: 'GET', uri: '{slug}', //handler: [BlogController::class, 'show'], handler: function (string $slug) { return $createdResponse; }, )->matches(new SlugMatches(resourceKey: 'blog')); $app->route( method: 'GET', uri: '{slug}', //handler: [ProductController::class, 'show'], handler: function (string $slug) { return $createdResponse; }, )->matches(new SlugMatches(resourceKey: 'product'));
Using Locale
You may use the withLocale
parameter to define the name of the uri locale parameter. Once defined, slugs will be matched locale dependent.
use Tobento\App\Slugging\Routing\SlugMatches; $app->route( method: 'GET', uri: '{?locale}/{slug}', handler: function (string $slug) { return $createdResponse; }, ) ->locales(['de', 'en']) ->localeOmit('en') ->matches(new SlugMatches( resourceKey: 'blog', withLocale: 'locale', ));
Using The Resource Id
You may use the withUriId
parameter to define the name of the parameter passed to the handler whereby the resource id from the slug entity $slug->resourceId()
will be passed.
use Tobento\App\Slugging\Routing\SlugMatches; $app->route( method: 'GET', uri: '{slug}', handler: function (int|string $id) { return $createdResponse; }, )->matches(new SlugMatches( resourceKey: 'blog', withUriId: 'id', ));
Custom Slug Uri
You may use the uriSlugName
parameter to change the uri name of the slug.
use Tobento\App\Slugging\Routing\SlugMatches; $app->route( method: 'GET', uri: '{alias}', handler: function (string $alias) { return $createdResponse; }, )->matches(new SlugMatches( resourceKey: 'blog', uriSlugName: 'alias', ));
Unique Slug Validation Rule
Requirements
It requires the App Validation:
composer require tobento/app-validation
Do not forget to boot the validator:
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\Validation\Boot\Validator::class); $app->boot(\Tobento\App\Slugging\Boot\Slugging::class); // Run the app $app->run();
Unique Slug Rule
use Tobento\App\Slugging\Validation\UniqueSlugRule; $validation = $validator->validate( data: [ 'slug' => 'login', 'slug.de' => 'anmelden', 'slug.en' => 'login', ], rules: [ 'slug' => [ new UniqueSlugRule( locale: 'en', // you may specify a custom error message: errorMessage: 'Custom error message', ), ], 'slug.de' => [ new UniqueSlugRule(), // locale is automatically determined as 'de'. ], 'slug.en' => [ new UniqueSlugRule(), // locale is automatically determined as 'en'. ], ] );
Skip validation
You may use the skipValidation
parameter in order to skip validation under certain conditions:
use Tobento\App\Slugging\Validation\UniqueSlugRule; $validation = $validator->validate( data: [ 'slug.en' => 'login', ], rules: [ 'slug.en' => [ // skips validation: new UniqueSlugRule(skipValidation: true), // does not skip validation: new UniqueSlugRule(skipValidation: false), // skips validation: new UniqueSlugRule(skipValidation: fn (mixed $value): bool => $value === 'foo'), ], ] );