Extensions

Shard Extensions to make your documents super charged.

Add role based permissions to your documents.

The Access Control extension allows you to set user access permissions on a document with simple annotations.

Configuration

Access Control has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.accessControl' => true
    ],
    ...
]);

However, Access Control requires a configured user service which is an instance of Zoop\Common\User\RoleAwareUserInterface. See User Config

Annotations

@Shard\AccessControl

The @Shard\AccessControl annotation is a document annotation that can contain a list of permission annotations which define who can access the document and what level of access they have. Eg:

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     ...
 * })
 */
class MyDoc {...}

@Shard\Permission\Basic

The Access Control extension provides the Basic permission. (Other extensions may provide other kinds of permission.) The Basic permission has three arguments:

Name type default description
allow string | array null A role, or array of roles that are allowed.
roles string | array null A role, or array of roles that the permission applies to.
allow string | array null An action, or array of actions that are allowed.
deny string | array null An action, or array of actions that are denied.

Access Control defines four actions (other extensions may define further actions):

Name description
create Persist a new instance of the document to the database.
read Read documents of this type from the database.
update::$field Change fields on an instance of this document which has already been persisted.
delete Perminently delete this type of document from the database.

So, for example, the following annotations would allow users with the guest permission to create, and read.

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="guest", allow={"create", "read"})
 * })
 */
class Simple {...}

Events

Most access control checks happen during a $documentManager->flush(). Therefore, when an access control check fails, an exception is not raised, as that would prevent a flush from completing correctly. Rather, an event is raised. The following events may be listened to:

Name description
createDenied Fires if create is attempted and denied.
updateDenied Fires if update is attempted and denied.
deleteDenied Fires if delete is attempted and denied.

Note: there is no event for a rejected read. This is because read access control is achieved through query filters, meaning both Doctrine and Shard are unaware if, or how many documents may have been filtered out by read access control.

Default permission

If a permission is not allowed, then it is always denied.

So, in this example there isn't any user who can update or delete.

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="guest", allow={"create", "read"})
 * })
 */
class Simple {...}

The * wildcard

The * can be used to glob role or action names.

In this example all users are allowed to read, editors are also allowed to create, and admins can do everything.

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="*", allow="read"),
 *     @Shard\Permission\Basic(roles="editor", allow="create"),
 *     @Shard\Permission\Basic(roles="admin", allow="*")
 * })
 */
class Simple {...}

Specific actions take precidence over wild cards in the same Permission. Eg, editors are allowed to all actions except delete

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="*", deny="delete")
 * })
 */
class Simple {...}

Order of permissions

Permissions are read in the order they are listed, so permissions lower on the list can override permissions higher on the list. Eg, if a user with the role editor tries to create they will be allowed, because the later permission overrides the earlier permission.

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles={"guest", "editor"}, allow="read"),
 *     @Shard\Permission\Basic(roles="editor", allow="create")
 * })
 */
class Simple {...}

Users with multiple roles

Users may have more than one role. Eg, if a user has only the editor role, they they will be allowed to create, but not be allowed to read. If a user has both the guest and editor role, they will be allowed to read and create.

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="guest", allow="read"),
 *     @Shard\Permission\Basic(roles="editor", allow="create")
 * })
 */
class Simple {...}

Update actions

Update actions are related to individual fields, not whole documents. To allow all fields to be updated, use the * wildcard. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="update::*")
 * })
 */
class Simple {...}

To allow update on a specific field, use the field name. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="update::description")
 * })
 */
class Simple {...}

To allow update on all fields, except some:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="update::*", deny={"update::lockedField1", "update::lockedField2"})
 * })
 */
class Simple {...}

Access Controller Service

Use the Access Controller service's areAllowed method to check if the configured user has permission to do actions on a document.

AccessController::areAllowed arguments

Name type description
actions array An array of action names to check.
metadata ClassMetadata The metadata for the document type being checked. This argument doesn't have to be passed, but is required if checking the create action, because no document instance exists before it is created.
document Object A document instance to check permissions against. Not required when checking create action.

This method will return an AllowedResult object.

For example:

$accessController = $manifest->getServiceManager->get('accessController');

if ( ! $accessController->areAllowed('update::name', null, $mydocument)->getAllowed()){
    //configured user is not allowed to update the name field of $mydocument;
}

Event based annotation handling.

The Annotation Extension is a low level extension that handles reading all the @Shard annotations. It is automatically enabled by any extension that requires annotations, so it can normally be ignored.

Hash or encrypt document fields.

Configuration

Access Control has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.crypt' => true
    ],
    ...
]);

Hash

A hash is a one way encryption method. That is, once the text is encrypted, you can't get the plain text back. It is especially useful for user passwords.

@Shard\Crypt\Hash

To hash a field, just add the @Shard\Crypt\Hash annotation to that field. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\Hash
     */
    protected $password;

    ...
}

Hash Salt

It is wise to use a salt when hashing to make cracking the encryption harder.

Salt stored in Document

By default, the crypt extension will check if your document implements Zoop\Common\Crypt\SaltInterface. If so, that will be used to retireve a salt. Eg:

use Zoop\Common\Crypt\SaltInterface;
use Zoop\Shard\Crypt\SaltGenerator;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument implements SaltInterface {

    /**
     * @ODM\String
     * @Shard\Crypt\Hash
     */
    protected $password;

    /**
     * @ODM\String
     */
    protected $salt;

    public function getSalt(){
        if (!isset($this->salt)){
            $this->salt = SaltGenerator::generateSalt();
        }
        return $this->salt;
    }

    ...
}

Note: if there are several hashed fields in the one document, this method will use the same salt for all.

Alternate salt

If you want to use a different salt, set the salt property of the annotation to a service name that will return an instance of Zoop\Common\Crypt\SaltInterface. Eg

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\Hash(salt='mysaltservice')
     */
    protected $password;

    ...
}

And configure your salt service in the Manifest:

$manifest = new Zoop\Shard\Manifest([
    'service_manager_config' => [
        'invokables' => [
            'mysaltservice' => 'My\Salt' //A class that implements SaltInterface
        ]
    ]
]);

Alternate Hash Algorithim

The default hash algorithim is in Zoop\Shard\Crypt\Hash\BasicHashService. If you would like to use an alternate hashing algorithim, set the service property of the annotation to a service name that will return an instance of Zoop\Shard\Crypt\Hash\HashServiceInterface. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\Hash(service='myhashservice')
     */
    protected $password;

    ...
}

And configure your hash service in the Manifest:

$manifest = new Zoop\Shard\Manifest([
    'service_manager_config' => [
        'invokables' => [
            'myhashservice' => 'My\Hash\Service' //A class that implements HashServiceInterface
        ]
    ]
]);

Block Cipher

A block cipher is a two way encryption method. That is, once the text is encrypted, you can get the plain text back.

@Shard\Crypt\BlockCipher

To encrypt a field, just add the @Shard\Crypt\BlockCipher annotation to that field. You must also set the name of a key service. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\BlockCipher(key="mykey")
     */
    protected $password;

    ...
}

Key Service

The key is used to encrypt and decrypt the field. Keep your keys safe! If someone steals your keys, then they can unlock your data.. The key is set with a key service which must return an instance of Zoop\Common\Crypt\KeyInterface. Eg:

$manifest = new Zoop\Shard\Manifest([
    'service_manager_config' => [
        'invokables' => [
            'mykey' => 'My\Key' //A class that implements KeyInterface
        ]
    ]
]);
use Zoop\Common\Crypt\KeyInterface;

class Key implements KeyInterface {

    public function getKey() {
        return 'my very secret key phrase';
    }
}

Block Cipher Salt

If you want to use a salt with the Block Cipher, you can.

Salt stored in Document

By default, the crypt extension will check if your document implements Zoop\Common\Crypt\SaltInterface. If so, that will be used to retireve a salt. Eg:

use Zoop\Common\Crypt\SaltInterface;
use Zoop\Shard\Crypt\SaltGenerator;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument implements SaltInterface {

    /**
     * @ODM\String
     * @Shard\Crypt\Hash
     */
    protected $password;

    /**
     * @ODM\String
     */
    protected $salt;

    public function getSalt(){
        if (!isset($this->salt)){
            $this->salt = SaltGenerator::generateSalt();
        }
        return $this->salt;
    }

    ...
}

Note: if there are several encrypted fields in the one document, this method will use the same salt for all.

Alternate salt

If you want to use a different salt, set the salt property of the annotation to a service name that will return an instance of Zoop\Common\Crypt\SaltInterface. Eg

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\BlockCypter(key='mykey', salt='mysaltservice')
     */
    protected $password;

    ...
}

And configure your salt service in the Manifest:

$manifest = new Zoop\Shard\Manifest([
    'service_manager_config' => [
        'invokables' => [
            'mykey' => 'My\Key',
            'mysaltservice' => 'My\Salt' //A class that implements SaltInterface
        ]
    ]
]);

Alternate Encryption Algorithim

The default block cypher algorithim is in Zoop\Shard\Crypt\BlockCypher\ZendBlockCypherService. If you would like to use an alternate algorithim, set the service property of the annotation to a service name that will return an instance of Zoop\Shard\Crypt\BlockCypher\BlockCypherServiceInterface. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

class MyDocument {
    /**
     * @ODM\String
     * @Shard\Crypt\BlockCypher(key="mykey", service='mycipherservice')
     */
    protected $password;

    ...
}

And configure your service in the Manifest:

$manifest = new Zoop\Shard\Manifest([
    'service_manager_config' => [
        'invokables' => [
            'mykey' => 'My\Key',
            'mycipherservice' => 'My\Cipher\Service' //A class that implements BlockCipherServiceInterface
        ]
    ]
]);

Freeze documents against updating or deleting.

Configuration

Freeze has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.freeze' => true
    ],
    ...
]);

Making a document freezable

To make a document freezable, a boolean field should be annotated with @Shard\Freeze. Eg:

/**
 * @ODM\Boolean
 * @Shard\Freeze
 */
protected $frozen = false;

For convienence you can use the Zoop\Shard\Freeze\DataModel\FreezableTrait to add such a field to a document. Eg:

use Zoop\Shard\Freeze\DataModel\FreezeableTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use FreezeableTrait;

    ...
}

Using the Freezer service

The freezer can be used to freeze and thaw documents. When frozen they cannot be updated or deleted. Note that the frozen state is not persisted until the DocumentManager is flushed. Eg:

$freezer = $manifest->getServiceManager()->get('freezer'); //get the freezer service
$freezer->freeze($myDocument); //freeze a document

$freezer->thaw($anotherDocument); //thaw a document

$manifest->getServiceManager()->get('mydocumentmanager')->flush() //flush to persist changes

Freeze and Thaw stamps

Timestamps

The freeze extension support automatic timestamping of freeze and thaw events. Use the @Shard\Freeze\FrozenOn and @Shard\Freeze\ThawedOn annotations. Eg:

/**
 * @ODM\Timestamp
 * @Shard\Freeze\FrozenOn
 */
protected $frozenOn;

/**
 * @ODM\Timestamp
 * @Shard\Freeze\ThawedOn
 */
protected $thawedOn;

Alternately you can use traits. Eg

use Zoop\Shard\Freeze\DataModel\FreezeableTrait;
use Zoop\Shard\Freeze\DataModel\FreezenOnTrait;
use Zoop\Shard\Freeze\DataModel\ThawedOnTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use FreezeableTrait;
    use FrozenOnTrait;
    use ThawedOnTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getFrozenOn();
$myDocument->getThawedOn();

User stamps

The freeze extension support automatic stamping with the active username on freeze and thaw events. Use the @Shard\Freeze\FrozenBy and @Shard\Freeze\ThawedBy annotations. This requires a configured user. Eg:

/**
 * @ODM\String
 * @Shard\Freeze\FrozenBy
 */
protected $frozenBy;

/**
 * @ODM\String
 * @Shard\Freeze\ThawedBy
 */
protected $thawedBy;

Alternately you can use traits. Eg

use Zoop\Shard\Freeze\DataModel\FreezeableTrait;
use Zoop\Shard\Freeze\DataModel\FreezenByTrait;
use Zoop\Shard\Freeze\DataModel\ThawedByTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use FreezeableTrait;
    use FrozenByTrait;
    use ThawedByTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getFrozenBy();
$myDocument->getThawedBy();

Access Conntrol

The Freeze extension can hook into the Access Control extension to allow or deny roles to the freeze and thaw actions. This requires the Access Control extension to be enabled, as well as the Freeze extension. Eg:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.accessControl' => true,
        'extension.freeze' => true
    ],
    ...
]);

Permissions can then be used as normal with the added actions of freeze and thaw. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="freeze", deny="thaw")
 *     ...
 * })
 */
class Simple {...}

Freeze Filter

The freeze extension provides a filter that can be used to remove frozen documents from result sets.

To filter out all frozen documents, use:

$documentManager->getFilterCollection()->enable('freeze');

To filter so only frozen documents are returned use:

$documentManager->getFilterCollection()->enable('freeze');
$filter = $documentManager->getFilterCollection()->getFilter('freeze');
$filter->onlyFrozen();

Events

Freeze provides the following events which can be subscribed to with the Doctrine EventManager:

Name description
preFreeze Fires before freeze happens.
postFreeze Fires after freeze happens.
preThaw Fires before thaw happens.
postThaw Firest after thaw happens.
freezeDenied Fires if a freeze is attempted but denied by access control.
thawDenied Fires if a thaw is attempted by denied by access control.
frozenUpdateDenied Fires if attempt is made to update a frozen document.
frozenDeleteDenied Fires if attempt is made to delete a frozen document.

Dynamically generate new resources based on document classes

The generator extension can be used to dynamically generate resources such as help files, javascript, and api documentation, from your document classes.

Configuration

Configuration is very important for the Generator extension. A resource_map must be defined. A resource_map is an array of resources that can be generated by the generator extension. The array keys are the resource names.

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.generator' => [
            'my resource name' => [
                /* resource generator config goes here */
            ]
        ]
    ],
    ...
]);

The configuration array for a resource may have three keys:

Name type required description
generator string true The service name which will return an instance of Zoop\Shard\Generator\GeneratorInterface
class string true The class name of a document class that will be used to generate the resource.
options array false An array of options that will be passed to the generator.

A complete config might look like:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.generator' => [
            'userDescription.html' => [
                'generator' => 'my.description.generator',
                'class' => 'My\Documents\User',
                'options' => [
                    'theme' => 'ocean_blue'
                ]
            ]
        ]
    ],
    ...
]);

The Resource Map

The resource map is a service provided by the generator extension. To get it use:

$resourceMap = $manifest->getServiceManager()->get('resourceMap');

To check if a resource can be generated:

if ($resourceMap->has('userDescription.html')){
    //resource can be generated
} else {
    //resource can't be generated
};

To generate a resource:

$html = $resourceMap->get('userDescription.html');

Create your own generator

To create your own generator, just implement Zoop\Shard\Generator\GeneratorInterface. Eg:

namespace My;

use Zoop\Shard\Generator\GeneratorInterface;

class DescriptionGenerator implements GeneratorInterface
{
    public function generate($name, $class, $options = null)
    {
        return "

You requested resource $name for $class be generated

"; } }

You will also need to register your generator with the service manager. This is most easily done when configuring the Manifest. Eg:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.generator' => [
            ...
        ]
    ],
    'service_manager_config' => [
        'invokables' => [
            'my.description.generator' => 'My\DescriptionGenerator'
        ],
        ...
    ]
]);

Add an owner field to documents, and enable the owner based access control.

Configuration

Owner has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.owner' => true
    ],
    ...
]);

However, the Owner extension requires a configured user service which is an instance of Zoop\Common\User\UserInterface. See User Config

Add an owner field

Place the @Shard\Owner annotation on a field.

/**
 * @ODM\String
 * @Shard\Owner
 */
protected $owner;

Alternately you can use traits. Eg

use Zoop\Shard\Owner\DataModel\OwnerTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use OwnerTrait;
    ...
}

The values of the field can be set and retrieved with:

$myDocument->setOwner();
$myDocument->getOwner();

Note: the value of the owner field is not automatically assigned to the active use when a document is created. It must be manually set.

Owner based access control

If the Access Control extension is enabled along with the Owner extension, the owner role can be used to allow or deny actions if the current user is equal to the owner field.

For example, the following access control annotations, only the owning user may read the document.

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="owner", allow="read")
 * })
 */

Access control owner field update

It is normally required to access control the ability to change who owns a document. Eg:

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="*",     allow={"create", "read"}                     ),
 *     @Shard\Permission\Basic(roles="owner", allow="update::*",       deny="update::owner"),
 *     @Shard\Permission\Basic(roles="admin", allow="update::owner"                        )
 * })
 */

In this example, all roles can read. Only the own can update a document. And only an admin can update the document owner.

Serialize document instances to array or json, and unserialize back to document instances.

The shard Serializer offers fine grained control over how documents are serialized and unserialized, with particluar mind to ajax and web clients.

Configuration

The serializer does not require any specific configuration.

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.serializer' => true
    ],
    ...
]);

However, some configuration options are available:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.serializer' => [
            'type_serializers' => [...]
            'max_nesting_depth' => [...]
        ]
    ],
    ...
]);
Name type default description
type_serializer array ['date' => 'serializer.type.dateToISO8601'] An array of type serializers. For more information see below.
max_nesting_depth integer 1 How deeply should the tree of references inside documents be followed when serializing.

Serializing Documents

First get the Serializer service, and then it can be used to serialize documents:

$serializer = $manifest->getServiceManager()->get('serializer');

$array = $serializer->toArray($myDocument); //serialize to array
$json = $serializer->toJson($myDocument);   //serialize to json

@Shard\Serializer\Ignore and @Shard\Unserializer\Ignore

Place on a document field to control if the field is serialized

Always ignore the field, eg:

/**
 * @ODM\String
 */
protected MyProperty;

/* or */

/**
 * @ODM\String
 * @Shard\Serializer\Ignore
 * @Shard\Unserializer\Ignore
 */
protected MyProperty;

Ignore a field only when serializing (not when unserializing):

/**
 * @ODM\String
 * @Shard\Serializer\Ignore
 */
protected MyProperty;

Ignore a field only when unserializing (not when serializing):

/**
 * @ODM\String
 * @Shard\Unserializer\Ignore
 */
protected MyProperty;

Reference Serializers

Document references can be serialized in several differnet ways.

RefLazy

By default references will be serialized to an array like this:

[$ref: 'CollectionName/DocumentId']

The $ref style of referencing is what Mongo uses internally.

The default behaviour uses the RefLazy serializer. However this can be overridden by defineing an alternative ReferenceSerializer as a property annotation.

Two alternate ReferenceSerializers are already included with Shard.

SimpleLazy

SimpleLazy will serialize a reference as the mongo id. It can be used like this:

/**
 * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
 * @Sds\Serializer\SimpleLazy
 */
protected $myDocumentProperty;

Eager

Eager will serialize references as if they were embedded documents. It can be used like this:

/**
 * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
 * @Sds\Serializer\Eager
 */
protected $myDocumentProperty;

When using the Eager serializer, the maxNestingDepth configuration option will control how deep the Eager serializer will go into a tree of references.

Custom Reference Serializer

You can create your own reference serializer to render references however you like. To do so, implement the Shard\Serializer\Reference\ReferenceSerializerInterface.

Then register your serializer with the service manager in the manifest config:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.serializer' => [
            ...
        ]
    ],
    'service_manager_config' => [
        'invokables' => [
            'my.reference.serializer' => 'My\ReferenceSerializer\Class'
        ],
        ...
    ]
]);

To use your reference serializer, use the annotation:

/**
 * @ODM\ReferenceMany(targetDocument="MyTargetDocument")
 * @Sds\Serializer\ReferenceSerializer("my.reference.serializer")
 */
protected $myDocumentProperty;

Date Serializer

Fields of type date are serialized by default with Shard\Serializer\Type\DateToISO8601.

To override this format, see Custom Type Serializers below

Custom Type Serializers

Each document field has an associated type, such as string or date. Serialization may be customized by type.

First create a class which implements the Shard\Serializer\Type\TypeSerializerInterface. You will need to define serialize and unserialize methods.

For example, this class will uppercase the first letter of every string when serializing, and lower case the first letter when unserializing:

use Shard\Serializer\Type\TypeSerializerInterface;

class MyStringSerializer implements TypeSerializerInterface {

    public static function serialize($value) {
        return ucfirst($value);
    }

    public static function unserialize($value) {
        return lcfirst($value);
    }
}

Then the class needs to be registered in the extension config and service manager:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.serializer' => [
            'type_serializers' => [
                'string' => 'my.string.serializer'
            ]
        ]
    ],
    'service_manager_config' => [
        'invokables' => [
            'my.string.serializer' => 'MyStringSerializer'
        ],
        ...
    ]
]);

The default Date serializer is an example of a Type Serializer which is regisered by default. To over ride it, simply register your own in the extension config.

Unserializing Documents

Get the unserializer from the service manager:

$unserializer = $manifest->getServiceManager()->get('unserializer');

Both fromArray and fromJson will unserialize an array or json into a document. They take up to four arguments:

Name type default description
data array | string The data to be unserialized. An array for fromArray, or a json string for fromJson.
className string null The class name of the document instance to create.
document object null If supplied, the unserializer won't attempt to load any document from the db, but use this one instead.
mode string unserialize_patch Must be either unserialize_update or unserialize_patch. If unserialize_update is used, the unserialized document will replace any existing document in the db with the same id. If unserialize_patch is used the unserialized data will be merged with any existing document in the db.

Mark documents as deleted, without actually deleting them.

Soft Deleted documents are simply marked as soft deleted, so they can be filtered out from result sets. Soft Deleted documents cannot be updated. However, note that Soft Deleted documents can still be fully deleted. If you need to control delete access, then use the Access Control extension.

Configuration

Soft Delete has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.softDelete' => true
    ],
    ...
]);

Making a document soft deletable

To make a document soft deleteable, a boolean field should be annotated with @Shard\SoftDelete. Eg:

/**
 * @ODM\Boolean
 * @Shard\SoftDelete
 */
protected $softDeleted = false;

For convienence you can use the Zoop\Shard\SoftDelete\DataModel\SoftDeletableTrait to add such a field to a document. Eg:

use Zoop\Shard\SoftDelete\DataModel\SoftDeletableTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use SoftDeletableTrait;

    ...
}

Using the SoftDeleter service

The SoftDeleter can be used to soft delete and restore documents. Note that the soft delete state is not persisted until the DocumentManager is flushed. Eg:

$softDeleter = $manifest->getServiceManager()->get('softDeleter'); //get the softDeleter service
$softDeleter->softDelete($myDocument); //soft delete a document

$softDeleter->restore($anotherDocument); //restore a document

$manifest->getServiceManager()->get('mydocumentmanager')->flush() //flush to persist changes

Soft Delete and Restore stamps

Timestamps

The soft delete extension supports automatic timestamping of soft delete and restore events. Use the @Shard\SoftDelete\SoftDeletedOn and @Shard\SoftDelete\RestoredOn annotations. Eg:

/**
 * @ODM\Timestamp
 * @Shard\SoftDelete\SoftDeletedOn
 */
protected $softDeletedOn;

/**
 * @ODM\Timestamp
 * @Shard\SoftDelete\RestoredOn
 */
protected $restoredOn;

Alternately you can use traits. Eg

use Zoop\Shard\SoftDelete\DataModel\SoftDeletableTrait;
use Zoop\Shard\SoftDelete\DataModel\SoftDeletedOnTrait;
use Zoop\Shard\SoftDelete\DataModel\RestoredOnTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use SoftDeletableTrait;
    use SoftDeletedOnTrait;
    use RestoredOnTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getSoftDeletedOn();
$myDocument->getRestoredOn();

User stamps

The soft delete extension supports automatic stamping with the active username on soft delete and restore events. Use the @Shard\SoftDelete\SoftDeletedBy and @Shard\SoftDelete\RestoredBy annotations. This requires a configured user. Eg:

/**
 * @ODM\String
 * @Shard\SoftDelete\SoftDeletedBy
 */
protected $softDeletedBy;

/**
 * @ODM\String
 * @Shard\SoftDelete\RestoredBy
 */
protected $restoredBy;

Alternately you can use traits. Eg

use Zoop\Shard\SoftDelete\DataModel\SoftDeleteableTrait;
use Zoop\Shard\SoftDelete\DataModel\SoftDeletedByTrait;
use Zoop\Shard\SoftDelete\DataModel\RestoredByTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use SoftDeleteableTrait;
    use SoftDeletedByTrait;
    use RestoredByTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getSoftDeletedBy();
$myDocument->getRestoredBy();

Access Conntrol

The soft deleted extension can hook into the Access Control extension to allow or deny roles to the softDelete and restore actions. This requires the Access Control extension to be enabled, as well as the Soft Delete extension. Eg:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.accessControl' => true,
        'extension.softDelete' => true
    ],
    ...
]);

Permissions can then be used as normal with the added actions of softDelete and restore. Eg:

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="editor", allow="softDelete", deny="restore")
 *     ...
 * })
 */
class Simple {...}

Soft Delete Filter

The soft delete extension provides a filter that can be used to remove soft deleted documents from result sets.

To filter out all soft deleted documents, use:

$documentManager->getFilterCollection()->enable('softDelete');

To filter so only soft deleted documents are returned use:

$documentManager->getFilterCollection()->enable('softDelete');
$filter = $documentManager->getFilterCollection()->getFilter('softDelete');
$filter->onlySoftDeleted();

Events

Soft Delete provides the following events which can be subscribed to with the Doctrine EventManager:

Name description
preSoftDelete Fires before soft delete happens.
postSoftDelete Fires after soft delete happens.
preRestore Fires before restore happens.
postRestore Firest after restore happens.
softDeleteDenied Fires if a soft delete is attempted but denied by access control.
restoreDenied Fires if a restore is attempted by denied by access control.
softDeleteUpdateDenied Fires if attempt is made to update a soft deleted document.

Stamp documents with creation and update timestamp and user.

Configuration

Stamp has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.stamp' => true
    ],
    ...
]);

Create and Update stamps

Timestamps

The stamp extension supports automatic timestamping of create and update events on documents. Use the @Shard\Stamp\CreatedOn and @Shard\Stamp\UpdatedOn annotations. Eg:

/**
 * @ODM\Timestamp
 * @Shard\Stamp\CreatedOn
 */
protected $createdOn;

/**
 * @ODM\Timestamp
 * @Shard\Stamp\UpdatedOn
 */
protected $updatedOn;

Alternately you can use traits. Eg

use Zoop\Shard\Stamp\DataModel\CreatedOnTrait;
use Zoop\Shard\Stamp\DataModel\CreatedOnTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use CreatedOnTrait;
    use UpdatedOnTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getCreatedOn();
$myDocument->getUpdatedOn();

User stamps

The stamp extension supports automatic stamping with the active username on document create and update. Use the @Shard\Stamp\CreatedBy and @Shard\Stamp\UpdatedBy annotations. This requires a configured user. Eg:

/**
 * @ODM\String
 * @Shard\Stamp\CreatedBy
 */
protected $createdBy;

/**
 * @ODM\String
 * @Shard\Stamp\UpdatedBy
 */
protected $updatedBy;

Alternately you can use traits. Eg

use Zoop\Shard\State\DataModel\CreatedByTrait;
use Zoop\Shard\State\DataModel\UpdatedByTrait;

//Annotation imports
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/** @ODM\Document */
class MyDocument {

    use CreatedByTrait;
    use UpdatedByTrait;
    ...
}

The values of the fields can be retrieved with:

$myDocument->getCreatedBy();
$myDocument->getUpdatedBy();

Access Conntrol

The stamp extension can hook into the Access Control extension to provide the extra roles of creator and updater. Permissions can be allowed or denied if the current active user is the creator or updater. Eg:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.accessControl' => true,
        'extension.stamp' => true
    ],
    ...
]);
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Zoop\Shard\Annotation\Annotations as Shard;

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Basic(roles="creator", allow="update::*")
 *     ...
 * })
 */
class Simple {...}

Add state to documents and build document workflows.

A document has a state, such as 'draft' and can be transitioned to another state, such as 'published'.

Configuration

State has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.state' => true
    ],
    ...
]);

Adding state to a Document

To add state, add @Shard\State to a field. Eg:

/**
 * @ODM\String
 * @Shard\State
 */
protected $state;

For convienence you can use the Zoop\Shard\State\DataModel\StateTrait to add such a field to a document.

State Access Control

Access Control permissions can be tied to document state. Use @Shard\Permission\State.

Eg. These permissions allow all roles to read only when a document is in a published state. A writer role is allowed to create, read and update documents in a draft state.

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\State     (roles="*",      states="published", allow="read"                      ),
 *     @Shard\Permission\State     (roles="writer", states="draft",     allow={"create", "update", "read"}),
 * })
 */
class AccessControlled {
    ...
}

States can be listed as an array, and also support wilds. Eg:

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\State     (roles="*",      states="published", allow="read"                      ),
 *     @Shard\Permission\State     (roles="writer", states={"d*", "published"},     allow={"create", "update", "read"}),
 * })
 */
class AccessControlled {
    ...
}

Transition Access Control

Access Control permission can use used to control who can make changes to document state. Use @Shard\Permission\Transition

Eg. These permissions allow a writer to move a document from draft to reivew. A reviewer may move a document from review to draft or review to published. An admin can make any transition.

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Transition(roles="writer",   allow="draft->review"                       ),
 *     @Shard\Permission\Transition(roles="reviewer", allow={"review->draft", "review->published"}),
 *     @Shard\Permission\Basic     (roles="admin",    allow="*"                                   )
 * })
 */
class AccessControlled {
    ...
}

Transitions can have wilds. (very handy).

/**
 * @ODM\Document
 * @Shard\AccessControl({
 *     @Shard\Permission\Transition(roles="writer",   allow="draft->review"),
 *     @Shard\Permission\Transition(roles="reviewer", allow="review->*"    ),
 *     @Shard\Permission\Basic     (roles="admin",    allow="*"            )
 * })
 */
class AccessControlled {
    ...
}

State Filter

The state extension provides a filter that can be used to filter result sets based on document state.

The state filter takes a list of states, and if those states should be included or excluded.

Eg, exclude some states:

$documentManager->getFilterCollection()->enable('state');
$filter = $documentManager->getFilterCollection()->getFilter('state');
$filter->setStates(['inactive']);
$filter->excludeStateList();

Eg, include some states:

$documentManager->getFilterCollection()->enable('state');
$filter = $documentManager->getFilterCollection()->getFilter('state');
$filter->setStates(['published', 'draft']);
$filter->includeStateList();

Events

State provides the following events which can be subscribed to with the Doctrine EventManager:

Name description
preTransition Fires before state transition happens.
onTransition Fires when a transition is happening. That is, after access control checks but before a new document change set is prepared for document persistance.
postTransition Fires after a transition has happened.
transitionDenied Fires if Access Control has denied permission to make a requested transition.

Add validation to documents.

Validate fields, and whole documents so only quality data makes it into your database.

The Validator extensions uses the Mystique validator library.

Configuration

State has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.validator' => true
    ],
    ...
]);

Adding validation to a Field

To add a validator use annotations in the @Shard\Validator namespace. Eg:

/**
 * @ODM\String
 * @Shard\Validator\Length("min" = 5, "max" = 10)
 */
protected $myproperty;

To add a multiple validators in a chain, use @Shard\Validator\Chain. Eg:

/**
 * @ODM\String
 * @Shard\Validator\Chain({
 *     @Shard\Validator\Alpha,
 *     @Shard\Validator\Length("min" = 5, "max" = 10)
 * })
 */
protected $myproperty;

All the validators in the Mystique library are supported with their own annotation.

Custom Validators

To write your own validators, inherit from Zoop\Mystique\Base and use the @Shard\Validator annotation.

/**
 * @ODM\String
 * @Shard\Validator("class" = "My\Validator\Class", "options" = {"constuctor options array"})
 */
protected $myproperty;

Document validators

Sometimes you want a validation rule that interrogates more than one field. To do so, just use the @Shard\Validator annotation on a document, rather than a field. Eg:

/**
 * @ODM\Document
 * @Shard\Validator(class = "Zoop\Shard\Test\Validator\TestAsset\ClassValidator")
 */
class Simple {
    ...
}

The isValid method of the validator will be passed the complete document instance to validate.

Document Validator Service

To validate a document before flush, use the documentValidator service. This will validate all fields and any document validators. Eg:

$documentValidator = $manifest->getServiceManager()->get('documentValidator');
$result = $documentValidator->isValid($myDocument);

Events

Validator provides the following events which can be subscribed to with the Doctrine EventManager:

Name description
invalidUpdate Fires during flush if and updated document is invalid.
invalidCreate Fires during flush if and updated document is invalid.

Add zones to documents.

A zone is a region of relevance. For example, it may be a company department, a geographical area.

A document may be assigned multiple zones.

Configuration

Zone has no configuration options. Just use:

$manifest = new Zoop\Shard\Manifest([
    ...
    'extension_configs' => [
        'extension.zone' => true
    ],
    ...
]);

Adding zones to a Document

To add zones, add @Shard\Zones to a field. Eg:

/**
 * @ODM\Collection
 * @Shard\Zones
 */
protected $zones;

For convienence you can use the Zoop\Shard\Zone\DataModel\ZoneTrait to add such a field to a document.

Zone Filter

The zone extension provides a filter that can be used to filter result sets based on document zones.

The zone filter takes a list of zones, and if those zones should be included or excluded.

Eg, exclude some zones:

$documentManager->getFilterCollection()->enable('zone');
$filter = $documentManager->getFilterCollection()->getFilter('zone');
$filter->setStates(['business-support']);
$filter->excludeZoneList();

Eg, include some states:

$documentManager->getFilterCollection()->enable('zone');
$filter = $documentManager->getFilterCollection()->getFilter('zone');
$filter->setStates(['accounts', 'hr']);
$filter->includeZoneList();