- Structure and Logic
- General Usage
- Real Life Example
Let's start by defining terms used in this context.
The locator is the tool used to find resources. It know how many floor there is, what can be found on those floor and will actually search the floor for the resource. It's like the receptionist of the office building that tell you where to find the person you're looking for for.
Locations are possible places resources could be. Typically, each framework or package in our project will be added to the location list. Locations are the floors of our office building. It's assumed here each package (each floor) has the same structure.
A stream is the definition of what we can find. A stream is composed of a scheme and a path. The scheme defines what we are looking for. Are we looking for a person, a conference room, a picture, a template, etc? The path is the location of this element inside the location. Where, on each floor, we find people (at desks), picture (in the album) or the templates (in /style/template
).
The streams themselves creates a Uniform Resource Identifiers or URI in the form of schema://path
. URIs are a very strong concept that decouples resource location completely from the mechanisms of individual frameworks or in this case, locations. Furthermore, context-specific scheme can be used to simply a search path. For example, instead of file://Bundle/WebProfilerBundle/Resources/config/routing/wdt.json
, a config
scheme can be used to regroup everything related to the Bundle/WebProfilerBundle/Resources/config
path: config://routing/wdt.json
. To relate to our office building metaphor, the URI is the question you ask the receptionist when you're looking for someone.
A resource is what you are looking for. A resource could be a template file, a configuration file, an image or any other kind of tangible asset in your project.
In this concept, multiple locations can contain the same resource. When looking for a specific resource without any knowledge of the location it's in, we can't be presented with both. One most win over the other. This is why locations include the concept of priority loading. Simply put, the last location added wins.
It's just like searching the office building, top floor to bottom floor and stopping once you found that guy Greg you were looking for. There might be a Greg a floor below, but we don't care. Top floor Greg wins. This might seams cruel, but when using multiple external packages, you might need to overwrite something one defines with more restrictive of customized data.
A consequence of using URIs for identifying resources is that they are wrapped a stream wrapper around the resource locator. While the locator can return the full path of a resource or other informations using the resource model, the stream wrapper make it so a resource URI can be used directly with built-in PHP functions such as fopen
and file_get_contents
. For example :
echo file_get_contents('config://routing/wdt.json');
A shared stream lives outside of our packages structure, where we can find shared resources. To use our office building analogy, it's like the parking garage. Cars can't be found on floors, they belong to the garage. They can also be associated to or used by any floor. So when searching for cars, we won't even looks at the different floors. In other words, a shared stream is not influenced by the locations.
In a software environment, this can be seen as a directory used to write log files for example. A log is not tied to a specific framework or location. They can all write info to it.
When getting info about a particular resource, the locator will typically return instance of the Resource model. This model is essentially a representation of a file/location and it's metadata. Those metadata can be used to get the path of the resource. It can also be used to get more detailed informations including in which location the file was found.
When working with shared streams, prefix can be used to manually define a subpath. Let's look at different stream defined using the cars
scheme :
Prefix | Path | Uri | Search | Real Path |
---|---|---|---|---|
Building/cars/ | cars://police/blah.txt | police/blah.txt in Building/cars | Building/cars/police/blah.txt | |
police | Building/cars/police/ | cars://police/blah.txt | blah.txt in Building/cars/police | Building/cars/police/blah.txt |
rental | Rental/ | cars://rental/blah.txt | blah.txt in Rental/ | Rental/blah.txt |
You can see how a prefix
can be used so the cars://rental
URI act as a proxy for the /Rental
directory. Note on the above table, the first two rows result in the same file being found. Of course this is basically useless, but it shows why you should be careful with prefix and what it's not. Note that a prefix will always overwrite a normal path (one without a prefix).
This also means if there's a file located in Building/cars/rental/blah.txt
(the first search path), the cars://rental/blah.txt
URI won't return the rental/blah.txt
file from the prefix-less search path. Instead, blah.txt
will be returned from the rental
prefix search path.
Of course, prefix can also be used with non shared streams. Using the same streams :
Prefix | Path |
---|---|
files/ | |
data | upload/data/files/ |
The resulting search paths will take the Floors
locations into account :
Uri | Search Path |
---|---|
files://test.json | Floors/{floorX}/files/ |
files://data/test.json | Floors/{floorX}/upload/data/files/ |
Shared and non shared streams can also be mixed when using prefix :
Prefix | Path | Shared |
---|---|---|
files/ | no | |
data | upload/data/files/ | yes |
The resulting search paths will then be :
Uri | Search Path |
---|---|
files://test.json | Floors/{floorX}/files/ |
files://data/test.json | upload/data/files/ |
In other words...
See API documentation for more information.
$locator = new ResourceLocator();
The locator accept a single optional argument, $basePath
. This can be used to define the base search path for the locator. In most cases, it will be project root folder.
A stream can either be created directly or an existing stream can be registered with the locator.
$stream = new ResourceStream();
$locator->addStream($stream);
$locator->registerStream($scheme, $prefix, $path, $shared);
Similar to streams, a location can either be created or an existing one can be registered with the locator.
$location = new ResourceLocation();
$locator->addLocation($location);
$locator->registerLocation($name, $path);
The findResource
and findResources
methods can be used to find paths for the specified URI. While findResource
will return the top most file, findResources
will return all the resources available for that URI, sorted by priority.
$locator->findResources('config://default.json');
/*
[
'app/PackageA/config/default.json',
'app/PackageB/config/default.json',
'app/PackageC/config/default.json'
]
*/
$locator->findResource('config://default.json');
// 'app/PackageA/config/default.json'
by default, absolute paths will be returned. Relative path can be returned by setting the absolute
flag to false.
getResource
and getResources
can be used the same way as findResource
and findResources
, but instead of returning the path for each assets, a Resource
instance will be returned. getResources
will return an array of all resources, sorted by priority.
All available resources in a given directory can be listed using the listResources
method. This method will also returns the resources recursively, unlike getResources
or findResources
.
$resources = $locator->listResources('cars://');
/*
[
'app/PackageA/config/develop.json',
'app/PackageA/config/testing.json',
'app/PackageB/config/default.json',
'app/PackageB/config/test/foo.json',
'app/PackageC/config/production.json',
]
*/
In the above example, if both PackageA
and PackageB
have a default.json
file, the top most version will be returned. To return all instances of every resources, the all
flag can be used :
$resources = $locator->listResources('cars://', true);
/*
[
'app/PackageA/config/default.json',
'app/PackageA/config/develop.json',
'app/PackageA/config/production.json',
'app/PackageA/config/testing.json',
'app/PackageB/config/default.json',
'app/PackageB/config/test/foo.json',
'app/PackageC/config/production.json',
]
*/
Resources can be represented using an instance of a class. This can be used to access different metadata about a file in addition to the file path.
Available methods :
-
getAbsolutePath()
: Returns the file absolute path. -
getPath()
: Returns the file relative path. -
getBasePath()
: Returns the path that comes after the://
in the resource URI. -
getBasename()
: Returns the trailing name component (ex.: foo/test.txt -> test.txt). -
getExtension()
: Returns the resource extension (foo/test.txt -> txt). -
getFilename()
: Returns the resource filename (foo/test.txt -> test). -
getLocation()
: Returns the location instance used to find the resource. Returns Null if it's a shared stream. -
getStream()
: Returns the stream instance used to find the resource. -
getUri()
: Returns the URI that can be used to retrieve this resource.
See the API Documentation for more informations.
By default, a resource instance will cast as a string containing the absolute path :
$resource = $locator->getResource(config://default.json);
echo $resource->getAbsolutePath();
// '/app/PackageB/config/default.json'
echo $resource;
// '/app/PackageB/config/default.json'
The locator provides some methods to control registered streams. Since stream scheme (the part before the ://
) is unique, most streams are identified using schemes.
-
getStream(string $scheme)
: Returns a stream instance from the stream scheme. ReturnStreamNotFoundException
if scheme doesn't match any registered stream. -
getStreams()
: Returns an array of all the streams registered on the locator. Each stream will be represented by an instance of Stream Class. -
isStream(string $uri)
: Returns true if URI is resolvable by using locator. To be resolvable, a URI must be valid and bound to a registered scheme. Any valid URI can be resolvable, either a file or a path. -
schemeExists(string $scheme)
: Return true or false if a scheme match a registered stream. -
listStreams()
: Return a list of all the stream scheme registered. -
removeStream(string $scheme)
: Unregister the stream associated with the specified scheme.
When interacting with stream instances, informations can be retrieved or changed using the instance public methods :
-
getPath
: Return the base path for the stream. In a non shared stream, it would be the relative path inside the location. -
getPrefix
: Return the prefix defined for the stream. -
getScheme
: Return the stream scheme (the part before the://
). -
isShared
: Return true or false if a stream is shared. -
setPath(string $path)
: Change the path for the stream. -
setPrefix(string $prefix)
: Change the stream prefix. -
setScheme(string $scheme)
: Change the stream scheme. A scheme shouldn't be changed once the stream is registered with the locator. This can produce unwanted behavior. -
setShared(bool $$shared)
: Change the stream shared status.
See the API Documentation for more informations.
The locator also provides methods to control registered locations. Each location have a name and a path.
-
getLocation(string $name)
: Returns a location instance for the location name. ReturnLocationNotFoundException
if the specified name doesn't match any registered location. -
getLocations()
: Returns an array of all the locations registered on the locator. Each location will be represented by an instance of location instance. -
listLocations()
: List all available location registered with the locator. Returns an associative array (name => path
). -
locationExist(string $name)
: Returns true or false if the specified location name is registered. -
removeLocation(string $name)
: Unregister the location associated with the specified scheme.
When interacting with location instances, informations can be retrieved or changed using the instance public methods :
-
getName
: Return the location name. -
setName
: Change the location name. A location name shouldn't be changed once it is registered with the locator. This can produce unwanted behavior. -
getPath
: Return the location base path. -
setPath
: Change the location base path.
See the API Documentation for more informations.