The Sprinkle Recipe

Every sprinkle needs a way to tell UserFrosting what it contains and how to use it. Without this information, UserFrosting wouldn't know which routes to register, which services to load, or how your sprinkle fits into the application. Imagine trying to bake a cake without a recipe—you'd have ingredients but no idea how to combine them.

The Sprinkle Recipe solves this problem. It's a simple PHP class that serves as your sprinkle's blueprint, declaring what your sprinkle provides: its name, location, routes, services, and dependencies. UserFrosting reads this recipe to integrate your sprinkle seamlessly into the application framework.

Every sprinkle must have a recipe—it's how UserFrosting knows your sprinkle exists and what it contains. This page explains the recipe's structure and how to configure each part.

Note

There is one exception where a sprinkle may not have a recipe: if it only provides static assets (like CSS or JS files) and no PHP code. In this case, the sprinkle does not need to be registered with a recipe since there are no dynamic components for the PHP runtime to consider. However, this is a rare case, and most sprinkles will require a recipe to function properly.

The SprinkleRecipe Interface

The Sprinkle Recipe is a simple PHP class that provides standard methods which will be called by services to retrieve information about your sprinkle structure and the class it's registering. Every sprinkle recipe MUST implement the UserFrosting\Sprinkle\SprinkleRecipe interface. If you started from the Skeleton, you already have a basic recipe.

This interface requires you to implement the following method in your recipe:

  • getName: Returns the name of the sprinkle.
  • getPath: Returns the path of the sprinkle.
  • getSprinkles: Returns an array of dependent sub-sprinkles recipe.
  • getRoutes: Return an array of routes classes.
  • getServices: Return an array of services classes.

Note

Since the class must implement the SprinkleRecipe interface, all of those methods are mandatory. Failure to implement the interface will result in an exception being thrown. However, it doesn't mean a method must return data. It's perfectly fine for a method to return an empty string or empty array.

Name

This method returns the name identifier of the sprinkle. This name is mostly used in debug interfaces to identify resources and classes registered by the sprinkle.

The method should return a string. For example:

public function getName(): string
{
    return 'My Application';
}

Path

This method returns the path of the sprinkle. This path should point where the src/, assets/, etc. folder is located, typically app/. For example, if your recipe is in app/src/YourSprinkle.php and your sprinkle structure looks like this...

├── app/
    ├── assets/
    ├── logs/
    ├── [...]
    ├── src/
        ├── [...]
        └── YourSprinkle.php
    └── [...]
├── public/
├── vendor/
├── composer.json
├── package.json
└── vite.config.ts

...getPath() should point to /app, or in this case the parent directory of where the recipe file is located :

public function getPath(): string
{
    return __DIR__ . '/../';
}

Note

app/ can actually be named whatever you want. As long as the recipe point to the folder containing all the sprinkle static resources.

Dependent sprinkles

This methods returns the sub-sprinkles your sprinkle depends on. This makes it easier to integrate other sprinkles' classes and resources into your app without having to copy everything inside your own recipe.

The order the sprinkles are loaded is important. Files in one sprinkle may override files with the same name and path in previously loaded sprinkles. For example, if we created site/templates/pages/about.html.twig, this would override core/templates/pages/about.html.twig because we load the site sprinkle after the core sprinkle.

Sprinkles will be loaded in the order you list them (top one first), but also based on their respective dependencies. For example:

public function getSprinkles(): array
{
    return [
        Core::class,
        Account::class,
        Admin::class,
    ];
}

Since Admin depends on Core and Account, it's not mandatory to relist them in your recipe. In fact, the code above is equivalent to this, since the other sprinkles will be registered by Admin:

public function getSprinkles(): array
{
    return [
        Admin::class,
    ];
}

This also means removing a dependency cannot be done by simply removing it from your recipe! For example, you cannot remove Account without also removing Admin, since Admin depends on Account.

However, it also means the next example is also equivalent:

public function getSprinkles(): array
{
    return [
        Admin::class,
        Account::class,
        Core::class,
    ];
}

Let's look at the process for the above code:

  1. Admin will be loaded first. Admin depends on Core first, and Account second. Core doesn't depend on anything. So Core is the first sprinkle loaded;
  2. Account is then checked. It depends on Core, which is already loaded, so Account is the second loaded sprinkle;
  3. Admin doesn't have any more dependencies not already loaded, so Admin is loaded third;
  4. This sprinkle's dependencies are all good, so it is loaded last.

Because of sprinkle dependencies, in all three examples the order will be Core -> Account -> Admin -> YOUR APP.

Tip

An easy way to see the final order sprinkles are loaded is via the command line php bakery sprinkle:list command. The registered sprinkles will be displayed in the order they are registered.

Routes

Return an array of routes classes. More details about this will be explored in Chapter 8 - Routes and Controllers.

For example, to register MyRoutes class:

public function getRoutes(): array
{
    return [
        MyRoutes::class,
    ];
}

Services

Return an array of services definitions. These will be explored in Chapter 7 - Dependency Injection

Example:

public function getServices(): array
{
    return [
        AlertStreamService::class,
        CacheService::class,
    ];
}

The main sprinkle

Since your sprinkle is the last loaded sprinkle, it becomes the main sprinkle. This is important, as the main sprinkle is the entry point to the app. The main sprinkle class must be referenced in two entry files : /public/index.php (web app/page entry) and /bakery (CLI App).

For example, if your main sprinkle class fully qualified name is UserFrosting\App\MyApp :

/public/index.php

// [...]

use UserFrosting\App\MyApp; // <--- Import here
use UserFrosting\UserFrosting;

$uf = new UserFrosting(MyApp::class); // <-- Reference here
$uf->run();

/bakery

// [...]

use UserFrosting\App\MyApp; // <--- Import here
use UserFrosting\Bakery\Bakery;

$bakery = new Bakery(MyApp::class); // <-- Reference here
$bakery->run();

Note

The main sprinkle class can be named whatever you want. You can rename the default one from App Skeleton, but it's important to remember to also update its reference in both locations.

Optional recipes

The sprinkle recipe power comes from its modularity. To avoid having one huge recipe with empty content, optional features can be added only when necessary.

The available sub-recipes includes:

Recipe Features
BakeryRecipe Registering Bakery commands
MigrationRecipe Registering Migrations
SeedRecipe Registering Seeds
MiddlewareRecipe Registering Middlewares
EventListenerRecipe Registering Event Listeners
TwigExtensionRecipe Registering Twig Extension

Your recipe simply needs to implement the corresponding interface. Classes may implement more than one interface if desired by separating each interface with a comma. For example :

class MyApp implements
    SprinkleRecipe,
    TwigExtensionRecipe,
    MigrationRecipe,
    EventListenerRecipe,
    MiddlewareRecipe,
    BakeryRecipe
{

Tip

Your sprinkle could even define its own recipe that you or other sprinkles could implement

BakeryRecipe

Interface : UserFrosting\Sprinkle\BakeryRecipe

Methods to implements :

  • getBakeryCommands : Return a list of Bakery commands classes

    Example:

    public function getBakeryCommands(): array
    {
        return [
            BakeCommand::class,
            ClearCacheCommand::class,
        ];
    }
    

MigrationRecipe

Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MigrationRecipe

Methods to implement :

  • getMigrations : Return a list of Migrations classes

    Example:

    public function getMigrations(): array
    {
        return [
            SessionsTable::class,
            ThrottlesTable::class,
        ];
    }
    

SeedRecipe

Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\SeedRecipe

Methods to implement :

  • getSeeds : Return a list of Seeds classes

    Example:

    public function getSeeds(): array
    {
        return [
            DefaultGroups::class,
            DefaultPermissions::class,
            DefaultRoles::class,
        ];
    }
    

MiddlewareRecipe

Interface : UserFrosting\Sprinkle\MiddlewareRecipe

Methods to implement :

  • getMiddlewares : Return a list of Middlewares classes

    Example:

    public function getMiddlewares(): array
    {
        return [
            CsrfGuardMiddleware::class,
            SessionMiddleware::class,
        ];
    }
    

EventListenerRecipe

Interface : UserFrosting\Event\EventListenerRecipe

Methods to implement :

  • getEventListeners : Allows to register Event Listeners

    Example:

    public function getEventListeners(): array
        {
            return [
                AppInitiatedEvent::class => [
                    RegisterShutdownHandler::class,
                    ModelInitiated::class,
                    SetRouteCaching::class,
                ],
                BakeryInitiatedEvent::class => [
                    ModelInitiated::class,
                    SetRouteCaching::class,
                ],
                ResourceLocatorInitiatedEvent::class => [
                    ResourceLocatorInitiated::class,
                ],
            ];
        }
    

TwigExtensionRecipe

Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\TwigExtensionRecipe

Methods to implement :

  • getTwigExtensions : Return a list of Twig Extension classes

    Example:

    public function getTwigExtensions(): array
    {
        return [
            CoreExtension::class,
            AlertsExtension::class,
        ];
    }
    

Removing default sprinkles

A default install, from the Skeleton, enables every default sprinkle. But your app may not require every feature provided by these default sprinkles. For example, you might not need the Admin sprinkle if you don't need any user management features.

In this case, two files need to be edited : composer.json and the Sprinkle Recipe.

  1. In /composer.json, remove the sprinkle from the Composer requirements :

    "userfrosting/sprinkle-admin": "^5.1",
    
  2. Since changes were made to composer.json, composer need to be updated (composer update).

  3. In the Sprinkle Recipe, Admin::class can be removed from the getSprinkles() method:

    public function getSprinkles(): array
    {
        return [
            Core::class,
            Account::class,
            //Admin::class,
        ];
    }
    

Note

Technically, the Core sprinkle IS optional. However, remember it provides pretty much every base feature of UserFrosting, including database support. Without any sprinkles, i.e. only the UserFrosting Framework, your app would be a very basic Slim Application with routes support.

Customizing a dependent sprinkle

Sometimes you may want to customize one of the dependent sprinkles. For example, you may want to remove all routes defined in the Account sprinkle. Or use only one migrations from the AwesomeStuff sprinkle. There's two easy way to customize dependent sprinkles, either by cherry-picking resources or extending the dependent sprinkle's recipe.

Cherry-picking resources

This method is best when you want a small number of resources from a dependent sprinkle. For example, when you want one migration from the AwesomeStuff sprinkle. The drawback is if the dependent sprinkle is updated, you may need to manually update your code. If you want to import many resources (but not all of them) from a dependent sprinkle, it's best to use the other method.

In this case, instead of adding the dependent sprinkle (in getSprinkles), you open the dependent sprinkle recipe and copy the code you want into your recipe.

Extending dependent recipe

This method is best used when you want to remove a small number of resources from a dependent sprinkle. As with the previous method, if the dependent sprinkle is updated, you may need to manually update your code. If you want to only one resource from a dependent sprinkle, it's best to use the previous method to import one, than to remove everything else.

For example, you may want to remove all routes defined in the Account sprinkle :


namespace UserFrosting\App;

use UserFrosting\Sprinkle\Account\Account;

/**
 * Overwrite main Account Sprinkle Class, to remove routes.
 */
class CustomAccount extends Account
{
    /**
     * {@inheritDoc}
     */
    public function getRoutes(): array
    {
        return [];
    }
}

In this case, instead of depending on Account in getSprinkles, you'll add CustomAccount in your sprinkle getSprinkles. All other methods from Account will be included via CustomAccount.

You'll then have two recipes in your sprinkle, e.g.: MyApp and CustomAccount, side by side. MyApp will still be main sprinkle, referenced in index.php and bakery, since CustomAccount is a dependency of MyApp.