Description

The description component allows you to obtain standardized values (e.g. titles, links, descriptions) for object instances or class names.

_images/cube.png

Problem

One problem that can present itself when developing a CMS system is how to present information about objects in a standardized way. This may happen when developing a content browser for example, or when rendering breadcrumbs in an object hierarchy, where the objects may be of disparate types.

One approach might be to implement interfaces (think HasTitleInterface, HasThumbnailInterface). But this forces the object to be modified, and you will still need a service to resolve the value, or resort to a default if none is set.

Solution

The description component presents a framework in which the problem can be solved effectively. It provides standard descriptors and a factory for descriptions. Clients can then query the description to see if it has a specific descriptor. It is your job to provide enhancers that will set the descriptors on the description.

For example, a Sonata Admin enhancer could use the metadata from the Sonata system to provide the title as Sonata would present it, and provide links to the pages where the object can be modified.

Workflow

  • Description is requested for a given object instance.
  • Description factory creates a new description and passes it to description enhancers.
  • Description enhancers add descriptors (e.g. title, edit URL, etc)
  • User can retrieve descriptors for an object from the description.

Usage

For example, if you have a App\Entity\Post entity and a App\Document\Page document, and you have created an enhancer (MyAdminEnhancer) for your admin system and is aware of these two objects.

<?php

$post = // get the post entity
$page = // get the page document
$myAdminMetadataFactory = // your admin system has a metadata factory!

$descriptionFactory = new DescriptionFactory([
    new MyAdminEnhancer($myAdminMetadataFactory);
]);

$description = $descriptionFactory->getDescriptionFor($post);

echo $description->get('std.title')->getValue();
// "My Blog Post"

echo get_class($description->get('std.title'))
// Psi\Component\Description\Descriptor\StringDesciptor

$description = $descriptionFactory->getDescriptionFor($page);
echo $description->get('std.title')->getValue();
// e.g. "About Us"

The component includes a number of standard descriptors by default, including a the (real) class FQN, URLs for viewing, updating or removing the instance, URLs for thumbnail images, etc.

Subject Resolvers

Sometimes an object may act as a proxy for another object (as is the case with CMF/Puli resources for example). In these cases it is desirable to describe the object that is proxied and not the proxy.

The description allows you to register “subject resolvers” which can “swap” the subject before the description is made.

The resolver must accept a subject and return a subject:

class MySubjectResolver implements SubjectResolverInterface
{
    public function resolve(Subject $subject): Subject
    {
        if ($subject->hasObject() && $subject->getClass()->isSubclassOf(MySpecialInterface::class)) {
            return Subject::createFromObject($subject->getObject()->getProxiedObject());
        }

        return $subject;
    }
}

Note

The subject ay not have an object, if you call the $subject->getObject() method you must ALWAYS first check that the subject has an object $subject->hasObject().

You can then add it to the description factory using the second argument:

<?php

use Psi\Component\Description\DescriptionFactory;
use Psi\Component\Description\Schema\Schema;
use Psi\Component\Description\Schema\StandardExtension;

$descriptionFactory = new DescriptionFactory([
    new MyAdminEnhancer($myAdminMetadataFactory);
], [
    new MySubjectResolver(),
]);

Now whenever an object implementing MySpecialInterface is given to the description the subject will be replaced.

Schema Validation

In order that all systems use the title field in the same way, with the same descriptor object, a schema system is provided.

This is a necessary evil, as if two enhancers use different descriptors for the same purpose (e.g. title, and name) then the consumer can no longer depend on a canonical way to retrieve the title.

When given to the factory the Schema ensures that only valid desciptor names and value objects are set, and that when trying to retrieve an invalid descriptor, useful exception messages are provided.

<?php

use Psi\Component\Description\DescriptionFactory;
use Psi\Component\Description\Schema\Schema;
use Psi\Component\Description\Schema\StandardExtension;

$schema = new Schema();
$scehma->register(new StandardExtension());

$descriptionFactory = new DescriptionFactory([
    new MyAdminEnhancer($myAdminMetadataFactory);
], [], $schema);

$description = $descriptionFactory->getDescriptionFor($page);
$description->get('invalid key'); // throws exception

Note

Schema validation is an unnecessary (though negligble e.g. 40μs vs. 20μs) overhead in production and it can be disabled by simply not passing the schema to the factory.