The Sprinkle Recipe dictate how your Sprinkle is built, like a blueprint. UserFrosting services and framework will use the information from the recipe to initiate some services, and expose classes your sprinkle provides for other service to use.
Each Sprinkle must have a recipe. It's not possible for a sprinkle to exist without a recipe, as it won't be possible to expose it's class and service to the framework. It's possible however to customize other Sprinkle, as we'll see later on this page.
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 recipes 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.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 some method to return an empty string or empty array.This method returns the name identifier of the Sprinkle. This name is mostly used in debug interfaces to identify resources and class registered by the sprinkle.
The method should return a string. For example:
public function getName(): string
{
return 'My Application';
}
This method returns the path of the Sprinkle. This path should point where the src/
, assets/
, etc. folder are 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
└── webpack.config.js
...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__ . '/../';
}
app/
can actually be named whatever you want. As long as the recipe point to the folder containing all the sprinkle static resources.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.
The 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,
AdminLTE::class,
];
}
Since Admin
depends on Core
, Account
and AdminLTE
, it's not mandatory to relist them in your recipe. In fact, the code above is equivalent to this, since the other one will be registered by Admin
already :
public function getSprinkles(): array
{
return [
Admin::class,
];
}
This also mean removing AdminLTE
as a dependency for example cannot be done by simply removing it from your recipe! It's impossible to remove AdminLTE
without removing Admin
, since Admin
cannot work without it's dependency.
However, it also means the next example is also equivalent:
public function getSprinkles(): array
{
return [
AdminLTE::class,
Admin::class,
Account::class,
Core::class,
];
}
Let's look at the process for the above code :
Because of every sprinkle dependencies, in all three examples the order will be Core -> Account -> AdminLTE -> Admin -> YOUR APP
.
php bakery sprinkle:list
command. The registered sprinkles will be displayed in the order they are registered.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,
];
}
Return an array of services definitions. Theses will be explored in Chapter 7 - Dependency Injection
Example:
public function getServices(): array
{
return [
AlertStreamService::class,
CacheService::class,
];
}
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();
The sprinkle recipe power comes from it's 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 need 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
{
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,
];
}
Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MigrationRecipe
Methods to implements :
getMigrations
: Return a list of Migrations classes
Example:
public function getMigrations(): array
{
return [
SessionsTable::class,
ThrottlesTable::class,
];
}
Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\SeedRecipe
Methods to implements :
getSeeds
: Return a list of Seeds classes
Example:
public function getSeeds(): array
{
return [
DefaultGroups::class,
DefaultPermissions::class,
DefaultRoles::class,
];
}
Interface : UserFrosting\Sprinkle\MiddlewareRecipe
Methods to implements :
getMiddlewares
: Return a list of Middlewares classes
Example:
public function getMiddlewares(): array
{
return [
CsrfGuardMiddleware::class,
SessionMiddleware::class,
];
}
Interface : UserFrosting\Event\EventListenerRecipe
Methods to implements :
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,
],
];
}
Interface : UserFrosting\Sprinkle\Core\Sprinkle\Recipe\TwigExtensionRecipe
Methods to implements :
getTwigExtensions
: Return a list of Twig Extension classes
Example:
public function getTwigExtensions(): array
{
return [
CoreExtension::class,
AlertsExtension::class,
];
}
A default install, from the Skeleton, enables every default Sprinkles. But it's not said your app requires every features provided by theses default sprinkles. For example, you might not need the Admin Sprinkle if you don't need any of the user management feature.
In this case, two files need to be edited : composer.json
and the Sprinkle Recipe.
In /composer.json, remove the sprinkle from the Composer required :
"userfrosting/sprinkle-admin": "^5.0",
Since changes were made to composer.json, composer need to be updated (composer update
).
In the Sprinkle Recipe, Admin:class
can be removed from the getSprinkles()
method:
public function getSprinkles(): array
{
return [
Core::class,
Account::class,
//Admin::class,
AdminLTE::class,
];
}
Sometime you may want to customize one of the dependent sprinkle. 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 the dependent sprinkles, either by cherry picking resources or extending the dependent sprinkle recipe.
This method is best when you want a small number of resources from a dependent sprinkle. For example, when you want one migrations from the AwesomeStuff
Sprinkle. The drawback is if the dependent sprinkle is updated, you might not have the most up to date 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 as a simple (in getSprinkles
), you simply open the dependent sprinkle recipe and copy the code you want into your recipe.
This method is best used when you want to remove a small number of resource from a dependent sprinkle. The drawback is if the dependent sprinkle is updated, you might not have the most up to date 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.
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 still a dependency of MyApp
.