PHPackages                             tobento/app-slugging - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. tobento/app-slugging

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

tobento/app-slugging
====================

App slugging support.

2.0(7mo ago)044↓33.3%1MITPHPPHP &gt;=8.4

Since Sep 9Pushed 7mo ago1 watchersCompare

[ Source](https://github.com/tobento-ch/app-slugging)[ Packagist](https://packagist.org/packages/tobento/app-slugging)[ Docs](https://www.tobento.ch)[ RSS](/packages/tobento-app-slugging/feed)WikiDiscussions 2.x Synced 1mo ago

READMEChangelog (4)Dependencies (14)Versions (6)Used By (1)

App Slugging
============

[](#app-slugging)

Slugging support for the app using the [Slugifier Service](https://github.com/tobento-ch/service-slugifier).

Table of Contents
-----------------

[](#table-of-contents)

- [Getting Started](#getting-started)
    - [Requirements](#requirements)
- [Documentation](#documentation)
    - [App](#app)
    - [Slugging Boot](#slugging-boot)
        - [Slugging Config](#slugging-config)
    - [Generating Slugs](#generating-slugs)
    - [Adding Slugs](#adding-slugs)
        - [Repository Resource](#repository-resource)
    - [Slug Repository](#slug-repository)
    - [Routing](#routing)
        - [Slug Matches](#slug-matches)
    - [Unique Slug Validation Rule](#unique-slug-validation-rule)
- [Credits](#credits)

---

Getting Started
===============

[](#getting-started)

Add the latest version of the app slugging project running this command.

```
composer require tobento/app-slugging

```

Requirements
------------

[](#requirements)

- PHP 8.4 or greater

Documentation
=============

[](#documentation)

App
---

[](#app)

Check out the [**App Skeleton**](https://github.com/tobento-ch/app-skeleton) if you are using the skeleton.

You may also check out the [**App**](https://github.com/tobento-ch/app) to learn more about the app in general.

Slugging Boot
-------------

[](#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

[](#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
----------------

[](#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](https://github.com/tobento-ch/service-slugifier) to learn more about it.

Adding Slugs
------------

[](#adding-slugs)

You may add slugs to prevent dublicate slugs or for routing purposes such as using the [Slug Matches](#slug-matches) on routes.

**From Config**

You may add slugs using resources directly in the [Slugging Config](#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

[](#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
---------------

[](#slug-repository)

By default, the slug repository is [added to the slugs](#adding-slugs) in the [Slugging Config](#slugging-config) whereby preventing dublicated slugs.

The advantage using the slug repository is that there will be just one query while [generating slugs](#generating-slugs) or when using the [Slug Matches](#slug-matches) if it is the only [added slug resource](#adding-slugs).

**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
-------

[](#routing)

First, you will need to install the [App Http](https://github.com/tobento-ch/app-http).

### Slug Matches

[](#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',
));
```

**Route Parameters**

The following route parameters will be added if a slug matches:

```
use Tobento\Service\Routing\RouterInterface;

$route = $app->get(RouterInterface::class)->getMatchedRoute();

$slug = $route->getParameter('slug.slug'); // string
$locale = $route->getParameter('slug.locale'); // string
$resourceId = $route->getParameter('slug.resourceId'); // null|string
$resourceKey = $route->getParameter('slug.resourceKey'); // null|string|int
```

Unique Slug Validation Rule
---------------------------

[](#unique-slug-validation-rule)

**Requirements**

It requires the [App Validation](https://github.com/tobento-ch/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'),
        ],
    ]
);
```

Credits
=======

[](#credits)

- [Tobias Strub](https://www.tobento.ch)
- [All Contributors](../../contributors)

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance68

Regular maintenance activity

Popularity9

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~78 days

Recently: every ~97 days

Total

6

Last Release

218d ago

Major Versions

1.x-dev → 2.02025-10-03

PHP version history (2 changes)1.0.0PHP &gt;=8.0

2.0PHP &gt;=8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/055d6a1b5c2384bb179c75ab0b55914231d898fdc4dffeb30770f81200e52206?d=identicon)[TOBENTOch](/maintainers/TOBENTOch)

---

Top Contributors

[![tobento-ch](https://avatars.githubusercontent.com/u/16684832?v=4)](https://github.com/tobento-ch "tobento-ch (13 commits)")

---

Tags

slugpackageappslugifiertobentoslugging

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/tobento-app-slugging/health.svg)

```
[![Health](https://phpackages.com/badges/tobento-app-slugging/health.svg)](https://phpackages.com/packages/tobento-app-slugging)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
