Chapter 7. Resource Management

Traditionally a PHP application deals directly with all kinds of files. Realizing a file upload is usually an excessive task because you need to create a proper upload form, deal with deciphering the $_FILES superglobal and move the uploaded file from the temporary location to a safer place. You also need to analyze the content (is it safe?), control web access and ultimately delete the file when it's not needed anymore.

FLOW3 relieves you of this hassle and lets you deal with simple Resource objects instead. File uploads are handled automatically, enforcing the restrictions which were configured by means of validation rules. The publishing mechanism was designed to support a wide range of scenarios, starting from simple publication to the local file system up to fine grained access control and distribution to one or more content delivery networks. This all works without any further ado by you, the application developer.

7.1. Static Resources

FLOW3 packages may provide any amount of static resources. They might be images, stylesheets, javascripts, templates or any other file which is used within the application or published to the web. Static resources may either be public or private:

  • public resources are automatically mirrored to the public web directory and are publicly accessible without any restrictions (provided you know the filename)

  • private resources are not published by default. They can either be used internally (e.g. as templates) or published with certain access restrictions

Whether a static package resource is public or private is determined by its parent directory. For a package Foo the public resources reside in a folder called Foo/Resources/Public/ while the private resources are stored in Foo/Resources/Private/. The directory structure below Public and Private is up to you and will be cloned to the web resources folder.

7.2. Persistent Resources

Data which was uploaded by a user or generated by your application is called a persistent resource. Although these resources are usually stored as files, they are never referred to by their path and filename directly but are represented by Resource objects.

Note

It is important to completely ignore the fact that resources are stored as files somewhere in FLOW3's directory structure – you should only deal with resource objects.

New persistent resources can be created by either importing or uploading a file. In either case the result is a new Resource object which can be attached to any other object. A resource exists as long as the Resource object is connected to another entity or value object which is persisted. If a resource is not attached to any other persisted object, it will be cleaned up at the end of the script run.

7.3. Importing Resources

Importing resources is one way to create a new resource object. The ResourceManager provides a simple API method for this purpose:

Example 7.1. Importing a new resource

class ImageController {

   /**
    * @inject
    * @var \F3\FLOW3\Resource\ResourceManager
    */
   protected $resourceManager;

   // ... more code here ...

   /**
    * Imports an image
    * 
    * @param string $imagePathAndFilename
    * @return void
    */
   public function importImageAction($imagePathAndFilename) {
      $newResource = $this->resourceManager->importResource($imagePathAndFilename);
      
      $newImage = $this->objectFactory->create('F3\MyPackage\Domain\Model\Image');
      $newImage->setOriginalResource($newResource);

      $this->imageRepository->add($newImage);
   }
}

The ImageController in our example provides a method to import a new image. Because an image consists of more than just the image file (we need a title, caption, generate a thumbnail, ...) we created a whole new model representing an image. The imported resource is considered as the "original resource" of the image and the Image model could easily provide a "thumbnail resource" for a smaller version of the original.

This is what happens in detail while executing the importImageAction method:

  1. The URI (in our case an absolute path and filename) is passed to the importResource method which analyzes the file found at that location.

  2. The file is imported into FLOW3's persistent resources storage using the sha1 hash over the file content as its filename. If a file with exactly the same content is imported it will reuse the already stored resource.

  3. The Resource Manager returns a new Resource object which refers to the newly imported file.

  4. A new Image object is created and the resource is attached to it.

  5. The image is added to the ImageRepository. Only from now on the new image and the related resource will be persisted. If we omitted that step, the image, the resource and in the end the imported file would be discarded at the end of the script run.

In order to delete a resource just disconnect the resource object from the persisted object, for example by unsetting originalResource in the Image object.

7.4. Resource Uploads

The second way to create new resources is uploading them via a POST request. FLOW3's MVC framework detects incoming file uploads and automatically converts them into Resource objects. In order to persist an uploaded resource you only need to persist the resulting object.

Consider the following Fluid template:

<f:form method="post" action="create" object="{newImage}" name="newImage" enctype="multipart/form-data">
   <f:form.textbox property="image.title" value="My image title" />
   <f:form.upload property="image.originalResource" />
   <f:form.submit value="Submit new image"/>
</f:form>

This form allows for submitting a new image which consists of an image title and the image resource (e.g. a JPEG file). The following controller can handle the submission of the above form:

class ImageController {

   /**
    * @inject
    * @var \F3\FLOW3\Resource\ResourceManager
    */
   protected $resourceManager;

   // ... more code here ...

   /**
    * Creates a new image
    * 
    * @param \F3\MyPacakge\Domain\Model\Image $newImage The new image
    * @return void
    */
   public function createAction(\F3\MyPacakge\Domain\Model\Image $newImage) {
      $this->imageRepository->add($newImage);
      $this->forward('index');
   }
}

Provided that the Image class has a title and a originalResource property and that they are accessible through setTitle and setOriginalResource respectively the above code will work just as expected.

7.5. Resource Publishing

Static Resources

By default static resources (usually provided by packages) are published to the web directory on the first script run and whenever packages are activated or deactivated. If resource files are added, removed or changed after the first run, they won't be published again. This behavior is desired in a production context where it would be to time intensive to check for updated resources on every run.

In a development context however, you'll gladly sacrifice some microseconds for the convenience of automatically updated resource files. This can be achieved by setting "resource: publishing: detectPackageResourceChanges" to yes – which is already the case in the Development context settings in FLOW3's standard distribution.

Published static resources can be used in Fluid templates via the built-in resource view helper:

<img src="{f:uri.resource(path: 'Images/Icons/FooIcon.png', package: 'MyPackage')}" />

Note that the package parameter is optional and defaults to the package containing the currently active controller.

Persistent Resources

Persistent resources are published on demand because FLOW3 cannot know which resources are meant to be public and which ones are to kept private. The trigger for publishing persistent resources is the generation of its public web URI. A very common way to do that is displaying a resource in a Fluid template:

<img src="{f:uri.resource(resource: image.originalResource)}" />

The resource view helper (f:uri.resource()) will ask the ResourcePublisher for the web URI of the resource stored in image.originalResource. The publisher checks if the given resource has already been published and if not publishes it right away.

A published persistent resource is accessible through a web URI like http://example.local/_Resources/Persistent/107bed85ba5e9bae0edbae879bbc2c26d72033ab.jpg. One advantage of using the sha1 hash of the resource content as a filename is that once the resource changes it gets a new filename and is displayed correctly regardless of the cache settings in the user's web browser. Search engines on the other hand prefer more meaningful filenames. For these cases the resource view helper allows for defining a speaking title for a resource URI:

<img src="{f:uri.resource(resource: image.originalResource, title: image.title)}" />

A URI produced by the above template would look like this: http://example.local/_Resources/Persistent/107bed85ba5e9bae0edbae879bbc2c26d72033ab/my-speaking-title.jpg.

You can define as many titles for each resource as you want – the resulting file is always the same, identified by the sha1 hash.

Mirror Mode

Publishing resources basically means copying files from a private location to the public web directory. Creating copies however comes with a little speed penalty and in some cases the size of duplicated resources can become an issue.

If your operating system supports symbolic links, you can speed up the publication process by telling FLOW3 to create symlinks instead of copies. This can be achieved through some setting in FLOW3's Settings.yaml:

FLOW3:
  resource:
    publishing:
      fileSystem:
        # Strategy for mirroring files: Either "copy" or "link"
        mirrorMode: link

7.6. Resource Stream Wrapper

Static resources are often used by packages internally. Typical use cases are templates, XML, YAML or other data files and images for further processing. You might be tempted to refer to these files by using one of the FLOW3_PATH_* constants or by creating a path relative to your package. A much better and more convenient way is using FLOW3's built-in stream package resources wrapper.

The following example reads the content of the file MyPackage/Resources/Private/Templates/SomeTemplate.html into a variable:

Example 7.2. Using the Package Resource Stream Wrapper

$template = file_get_contents('package://MyPackage/Private/Templates/SomeTemplate.html');

You are encouraged to use this stream wrapper wherever you need to access a static package resource in your PHP code.