Skip to content
dantleech edited this page Aug 19, 2014 · 11 revisions

This document aims to describe a node API which will combine the currently separate PHPCR NodeInterface and Sulu Structure classes into one class. A side-effect of this goal will be the ability to map node types to classes - giving us a type of "object mapper".

This will make mapping PHPCR nodes to structures a non-issue and additionally enable us to cast different classes depending on their role, so for example content nodes can be of a certain class, whilst route nodes could be of another.

Wrapping

One of the aims of this refactoring is to enable domain specific classes - i.e. we should cast a specific class for a spefici type of node.

PHPCR is an interface. The objects that we receieve from PHPCR are concrete instances of vendor-specific implementations of the PHCPR interface, therefore we cannot simply extend these classes - doing so would bind us completely to a specific PHPCR vendor.

We are therefore forced to wrap these vendor objects.

Replacing the Structure class

Structures are generated by Sulu from the templates (which are stored in XML files). The generated structures are kept in the cache directory and extend the Structure base class

The ContentMapper then maps values from the PHPCRNode to the Structure class.

sulu:structure node type

I propose that we make all node types in content extend from a sulu:structure node type.

This node type would be a simple definition:

[sulu:structure] > nt:unstructured

And as you can see from above it would exact exactly like the node type nt:unstructured but would allow us to do two things:

  • SELECT FROM [sulu:structure] WHERE ...: Target SQL queries
  • Cast sulu:structure nodes to domain objects.

The second point is very beneficial, as it allows us to most (if not all) the logic in the content mapper within a domain object (f.e. Sulu\Bundle\CoreBundle\Node\Structure).

The unified Structure

So we are effectively going to unify the PHPCR\NodeInterface class and the Structure class so that the "mapping" operation done by ContentMapper is not required.

System properties

Current persisted system properties:

Name Type Description Notes
{locale}shadowBaseLanguage String Language to use when shadow is enabled
{locale}nodeType integer ?
{locale}creator Integer ID of user that created this node
{locale}changer Integer ID of user that last changed this node
{locale}created DateTime Date was created
{locale}state Integer
{locale}navContexts Array Navigation contexts Should this be here? Could this be an extension or plugin?
{locale}published Date Time Date node was published
{locale}enabledShadowLanguages Array List of languages that are "shadowed"
{locale}isShadow Boolean If the node in the prefixed language is a shadow

Runtime properties

(i.e. items which are inferred from the environment and the persisted properties):

Name Description
{locale}webspaceKey Determined from the environment
{locale}languageCode Inferred from env. and persisted properties
{locale}globalState Determined from state of this and ancestor nodes
{locale}concreteLanguage Determined by comparing all the existing languages against the "shadow" languages

###Proposed API

$structure = $session->getNode('/sulu/sulu_io/contents/hello');

# Locale methods
(string)  $structure->getLocale(); // COMPUTED: return the runtime locale of the node (aka LanguageCode)
(array)   $structure->getLocales(); // get all mapped locales on this node (including shadows)
(array)   $structure->getConcreteLocales(); // COMPUTED: return the concrete locales (i.e. locales which are not shadows)
(string)  $structure->getShadowBaseLocale();
(array)   $structure->getShadowLocales(); // return the locales for which shadowing is enabled
(boolean) $structure->isShadow(); // COMPUTED: If the current node is a shadow of a different concrete language

# System methods
(integer) $structure->getRole();  // CONTENT, INTERNAL_LINK, EXTERNAL_LINK (aka NodeType)
(integer) $structure->getCreator();
(integer) $structure->getChanger();
(integer) $structure->getState();
(integer) $structure->getComputedState(); // COMPUTED: aka. Global state, aka. Inherited State
(boolean) $structure->getPublished();
(string)  $structure->getWebspaceKey(); // COMPUTED: do we really need this?

// Navigation
(array)   $structure->getNavContexts();

Changes from the existing API

  • Renamed getNodeType to getRole: "NodeType" conflicts with the PHPCR concept. Role implies that this node "acts as" something i.e. acts as content, internal_link, etc.
  • Renamed getGlobalState to getComputedState
  • Various changes for the locale methods.

Structure Properties and Extensions

Properties

Issues:

  • The existing Sulu\...\Property class conflicts with the PHPCR concept of Property.
    • Renmae: Property => Field
    • Leave as Property and wrap the PHPCR property (so its still the property
  • The property is a mix of the attributes of the PHPCR property and the attributes of the "template" property.
    • It has both get/set Value and metadata about the property in the same class
  • ContentType refers to the type of the property. It should be renamed to PropertyType.

Solutions:

  • Rename: Property => StructureProperty
  • Renmae: Property => PropertyTemplate
    • Remove get|setValue
    • Add getPropertyTemplate method to the PHPCR node.

Break "template" property class out

$node = $sesssion->getNode('/foo');

$maxOccurs = $node->getProperty('foobar')->getTemplate()->getMaxOccurs();
$tags = $node->getProperty('foobar')->getTemplate()->getTags();

Where getProperty would return the PHPCR property (either the original or a wrapped one).

Cache maps

Wrapping the properties:

class SomeStructureCache extends Structure
{
    public function getProperties()
    {
        foreach (parent::getProperties() as $phpcrProperty) {
            $this->addChild(new StructureProperty(
                $phpcrProperty,

        }
    }
}

Graphing the property attributes:

class SomeStructureCache extends Structure
{
    public function init()
    {
        $this->getProperty('???')->setFoo();
        $this->getProperty('???')->setFoo();
    }
}

Doesn't work: We need an abstraction to access the translated properties.

Field abstraction

We separate the access of the raw PHPCR property and the "sulu" property by adding the concept of the field:

class SomeStructureCache extends Structure
{
    public function init()
    {
        $field1 = new Field('article');
        $field1->setAttributes(array(
            'title' => array(
                'de' => 'Ort',
                'en' => 'Place',
            ),
        ));
        $this->addField($field1);
    }
}

$someStructure = $session->get('/some/structure');

$someStructuture->setFieldLocale('de');

// access the field value through the field proxy
$article = $someStructure->getField('article')->getValue();
$article = $someStructure->getProperty('i18n:de-article')->getValue();