The locator service goal is to provides an abstraction layer for the different files and folders available across different sprinkles inside UserFrosting. In other words, it is a way of aggregating many search paths together. As you've seen in the previous chapters, each sprinkle can provides multiple resources and sprinkles can have the ability to overwrite a previous sprinkle resources. All of those resources, combined with the overwriting properties of a sprinkle is handled by the locator service. Templates and config files are a good example of those resources.
While the locator can be used to find files inside the sprinkles main directory (i.e. app/
), it can't be directly used to handle PHP Class Inheritance. The Dependency Injection needs to be used in such cases. The locator can however be used where it's necessary to list PHP files.
The locator uses the concept of Streams and Locations to represent directories and Sprinkles respectively. Each active sprinkle is registered as a Locator Location on boot. Several default streams are registered by the Core Sprinkle.
Two types of streams are available within the locator : shared and non-shared streams. The shared streams (or normal streams) represent a directory within the Sprinkles folder, which can usually be found in every sprinkles. Shared streams exists only in one place. Note that both type can coexist. For example, a stream can be defined twice, once shared and once non-shared. This allows to find resources inside each sprinkles as well as inside the public directory.
The following streams are defined by default by UserFrosting :
Stream | Defined By | Path | Shared | Description / Use |
---|---|---|---|---|
sprinkles | Core Sprinkle | ./ |
No | Generic path to each sprinkles |
config | Core Sprinkle | ./config |
No | Config files location |
extra | Core Sprinkle | ./extra |
No | Misc directory, used to store files unrelated to any other stream |
locale | Core Sprinkle | ./locale |
No | Translation files |
schema | Core Sprinkle | ./schema |
No | Request Schema files |
templates | Core Sprinkle | ./templates |
No | Templates files |
cache | Core Sprinkle | ./cache |
Yes | Shared cache directory |
database | Core Sprinkle | ./database |
Yes | Location of any file based database, for example SQLite database |
logs | Core Sprinkle | ./logs |
Yes | Shared log directory |
sessions | Core Sprinkle | ./sessions |
Yes | Shared sessions directory |
storage | Core Sprinkle | ./storage |
Yes | The local disk file storage |
public | Core Sprinkle | /public |
Yes | Absolute path to the public directory |
assets | Core Sprinkle | public://assets |
Yes | Path to the public assets directory. Sub-stream of public |
The paths for non-shared streams are calculated relatively from each sprinkle path, usually ./app
. The paths for shared streams are relative from the Main Sprinkle path only, unless otherwise noted.
sprinkles
stream can be used as wildcard to access pretty much anything inside a sprinkle without defining a new custom stream.The bakery command provide a debugging command to help you understand and validate all the paths returned by the locator service:
$ php bakery debug:locator
This command displays :
The locator provides many public methods for managing streams, locations and resources. But the most important tools provided by the locator is getting resources and listing resources. As stated earlier, this will allows you to find files and directories across multiple sprinkles, while respecting the sprinkle order or priority, without having to manually loop all sprinkles manually.
The first step is to inject the locator service in your class through Autowiring or Annotation injection on the UserFrosting\UniformResourceLocator\ResourceLocatorInterface
class:
use DI\Attribute\Inject;
use UserFrosting\UniformResourceLocator\ResourceLocatorInterface;
class MyClass
{
#[Inject]
protected ResourceLocatorInterface $locator;
public function myMethod(): void
{
// $this->locator...
}
}
or
use UserFrosting\UniformResourceLocator\ResourceLocatorInterface;
class MyClass
{
public function __construct(protected ResourceLocatorInterface $locator)
{
}
public function myMethod(): void
{
// $this->locator...
}
}
Whether you want to find a specific file or directory, the getResource
and getResources
methods will
Those two methods can be used to find paths for the specified URI. While getResource
will return the top most file, getResources
will return all the resources available for that URI, sorted by priority. For example:
$this->locator->getResources('schema://default.json');
// RESULT :
/*
[
'app/sprinkles/Core/schema/default.json',
'app/sprinkles/Account/schema/default.json',
'app/sprinkles/MySite/schema/default.json',
]
*/
$this->locator->getResource('schema://default.json');
// RESULT :
// 'app/sprinkles/MySite/schema/default.json',
The locator will return an instance of the Resource
object (or an array of Resource
objects). These objects can be cast as string and will return the absolute path to the resource (file or directory). Further public methods can be used on the Resource object to get more information about the returned resource. For example, to return the sprinkle name where it was found :
$schema = $this->locator->getResource('schema://default.json');
echo $schema->getLocation()->getName();
// RESULT :
// 'MySite'
Note these methods can also work on directories URI :
$this->locator->getResources('schema://foo');
// RESULT :
/*
[
'app/sprinkles/Core/schema/foo',
'app/sprinkles/Account/schema/foo',
'app/sprinkles/MySite/schema/foo',
]
*/
All available resources in a given directory can be listed using the listResources
method. This method will also returns the resources recursively, unlike getResources
.
$resources = $this->locator->listResources('schema://');
// RESULT :
/*
[
'app/sprinkles/Core/schema/develop.json',
'app/sprinkles/Core/schema/testing.json',
'app/sprinkles/Account/schema/test/foo.json',
'app/sprinkles/MySite/schema/default.json',
]
*/
If every sprinkle have a default.json
file, only the top most version will be returned. To return all instances of every resources, the all
flag can be set to true
:
$resources = $this->locator->listResources('schema://', true);
// RESULT :
/*
[
'app/sprinkles/Core/schema/develop.json',
'app/sprinkles/Core/schema/testing.json',
'app/sprinkles/Core/schema/default.json',
'app/sprinkles/Account/schema/default.json',
'app/sprinkles/Account/schema/test/foo.json',
'app/sprinkles/MySite/schema/default.json',
]
*/
While the sprinkles
stream can be used to access pretty much anything inside a sprinkle, sometimes it can be useful to create your own stream. Not only will it create a shorter URI, but you can also accommodate different use case, such as the usage of a shared stream or multiple search path.
A new stream can be registered pretty much anywhere. If the stream is tied to a particular service, it could be easier to register the stream at the same time as this service. For example, if a foo
service uses the bar
stream and all access to the bar
stream is done inside the foo
service, then it make sense to register the bar
stream inside the service. However, the recommended method to register a new stream is through event listening.
When the locator service is initialized, the ResourceLocatorInitiatedEvent
will be dispatched by the global event dispatcher. Each sprinkle can listen for this even and use it to register their own stream on the locator service. In fact, even the default stream are registered this way!
To register a new service, a new instance of UserFrosting\UniformResourceLocator\ResourceStream
can be created and passed to the locator through the addStream
method.
For example, let's create a listener class that will register a foo
stream, and a bar
shared stream:
<?php
namespace UserFrosting\Sprinkle\Site\Listeners;
use UserFrosting\Sprinkle\Core\Event\ResourceLocatorInitiatedEvent;
use UserFrosting\UniformResourceLocator\ResourceStream;
class ResourceLocatorInitiated
{
/**
* Add a new `foo` stream.
*
* @param ResourceLocatorInitiatedEvent $event
*/
public function __invoke(ResourceLocatorInitiatedEvent $event): void
{
// Normal Stream
$stream = new ResourceStream('foo');
$event->locator->addStream($stream);
// Shared Stream with custom path
$stream = new ResourceStream('bar', path: 'foobar', shared: true),
$event->locator->addStream($stream);
}
}
The listener then need to be registered in the Sprinkle Recipe:
namespace UserFrosting\Sprinkle\Site;
// ...
use UserFrosting\Event\EventListenerRecipe; // <-- Add this
use UserFrosting\Sprinkle\Site\Listeners\ResourceLocatorInitiated; // <-- Add this
// ...
class MyApp implements
SprinkleRecipe,
EventListenerRecipe // <-- Add this
{
// ...
// Add this -->
public function getEventListeners(): array
{
return [
ResourceLocatorInitiatedEvent::class => [
ResourceLocatorInitiated::class,
],
];
}
//<--
// ...
}
For more information about the locator, you can refer to the detailed documentation, API reference and examples on the Framework repository.