Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Implicit Lock

DZone's Guide to

Practical PHP Patterns: Implicit Lock

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

As for the Coarse Grained Lock pattern, the Implicit Lock one is an orthogonal consideration on locking mechanisms which can be adapted to both pessimistic and optimistic variations.

The idea behind this pattern is that a locking mechanism must be consistently used: the client code is usually responsible for always locking the item interested by a transaction before acting on it (pessimistic variation), or equivalently always acquire and release the affected objects through a locking mechanism which check the modified version does not conflict with the current data (optimistic variation).

To avoid leaving the responsibility of locking to client code, which duplicates it in all the different places that access the data, you can factor your code so that the data storage automatically manages locking for you. An external entity like a framework, a library or an Object-Relational Mapper can implicitly activate a locking process when you checkout an object that is configured for being locked. With this pattern, client code can't forget to set up locks on the interested objects anymore, and neither can it follow a different process since the single entry point for data storage has an implicit locking policy.

Implementation

In practice, this pattern is a simple layer of abstraction built over locking and data access mechanisms. Since relational databases are still the most likely target of these patterns, I'll assume we are talking about an ORM layer here, which also takes care of locking.

The mandatory tasks to automate for an optimistic lock-savy session to work are:

  • storing the version count as a field on the object.
  • including the version in UPDATE queries so that they do not affect any rows in case the version is not current.
  • adding a version increment to the same UPDATE query so that the change is atomic.

In case of a pessimistic version, the tasks are:

  • checking no one else has locked the requested item
  • acquiring the exclusive lock
  • releasing the lock successfully on completion or on error.

In our example of an ORM as the data source, the normal implementation of find() and save() methods should be decorated so that every sensible method manages the lock by itself. Usually there is a central point which takes care of objects reconstitution and that can perform locking tasks on configuration.

Advantages

The greatest advantage of Implicit Locking is its security: when correctly implemented, locks are always in place in a single entry point and cannot be disabled by client code.

In fact, this pattern should always be favored over rolling a different locking mechanism eash time is needed.

Moreover, this pattern relieves client code from managing an orthogonal detail like locking, which can be configured application-wide.

Disadvantages

The main disavantage of this pattern is the danger that it becomes a leaky abstraction in some use cases; for example, pessimistic locks acquired automatically can lead to deadlocks difficult to foresee and difficult to expose with tests, since there are many combinations of objects that can lead to a deadlock.

Example

Doctrine 2 automates Optimistic Locking via a version field, as we have seen in the related article; thus it isa perfect example of Implicit Locking, since the only added burden on client code is the configuration of metadata for the version field.

We can argue that the hidden field necessary for maintaining the object version is another point of complexity in the client code, and we are right on track. However, this is due to the stateless nature of PHP and HTTP more than to Doctrine's fault. If you keep the object in memory (for instance in the session, or in a variable if your transaction does not span over multiple HTTP interactions), you'll never need to maintain the version field value yourself.

Here we will explore some Doctrine 2 code instead of client-side one like we did before. The chosen class is the standard persister, which encapsulates the interaction with the database so that version-related code is kept in one place. The persister provides a completely parametrized implementation of an Optimistic Lock.

<?php
namespace Doctrine\ORM\Persisters;

use PDO,
Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\ORMException,
Doctrine\ORM\OptimisticLockException,
Doctrine\ORM\EntityManager,
Doctrine\ORM\Query,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Mapping\MappingException,
Doctrine\ORM\Mapping\ClassMetadata;

/**
* A BasicEntityPersister maps an entity to a single table in a relational database.
*
* A persister is always responsible for a single entity type.
*
* EntityPersisters are used during a UnitOfWork to apply any changes to the persistent
* state of entities onto a relational database when the UnitOfWork is committed,
* as well as for basic querying of entities and their associations (not DQL).
*
* The persisting operations that are invoked during a commit of a UnitOfWork to
* persist the persistent entity state are:
*
* - {@link addInsert} : To schedule an entity for insertion.
* - {@link executeInserts} : To execute all scheduled insertions.
* - {@link update} : To update the persistent state of an entity.
* - {@link delete} : To delete the persistent state of an entity.
*
* As can be seen from the above list, insertions are batched and executed all at once
* for increased efficiency.
*
* The querying operations invoked during a UnitOfWork, either through direct find
* requests or lazy-loading, are the following:
*
* - {@link load} : Loads (the state of) a single, managed entity.
* - {@link loadAll} : Loads multiple, managed entities.
* - {@link loadOneToOneEntity} : Loads a one/many-to-one entity association (lazy-loading).
* - {@link loadOneToManyCollection} : Loads a one-to-many entity association (lazy-loading).
* - {@link loadManyToManyCollection} : Loads a many-to-many entity association (lazy-loading).
*
* The BasicEntityPersister implementation provides the default behavior for
* persisting and querying entities that are mapped to a single database table.
*
* Subclasses can be created to provide custom persisting and querying strategies,
* i.e. spanning multiple tables.
*
* @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @since 2.0
*/
class BasicEntityPersister
{
// less relevant methods omitted...

/**
* Executes all queued entity insertions and returns any generated post-insert
* identifiers that were created as a result of the insertions.
*
* If no inserts are queued, invoking this method is a NOOP.
*
* @return array An array of any generated post-insert IDs. This will be an empty array
* if the entity class does not use the IDENTITY generation strategy.
*/
public function executeInserts()
{
// ...

foreach ($this->_queuedInserts as $entity) {
// ...

if ($this->_class->isVersioned) {
$this->_assignDefaultVersionValue($this->_class, $entity, $id);
}
}

// ...

return $postInsertIds;
}

/**
* Performs an UPDATE statement for an entity on a specific table.
* The UPDATE can optionally be versioned, which requires the entity to have a version field.
*
* @param object $entity The entity object being updated.
* @param string $tableName The name of the table to apply the UPDATE on.
* @param array $updateData The map of columns to update (column => value).
* @param boolean $versioned Whether the UPDATE should be versioned.
*/
protected final function _updateTable($entity, $tableName, array $updateData, $versioned = false)
{
// ...

if ($versioned) {
$versionField = $this->_class->versionField;
$versionFieldType = $this->_class->getTypeOfField($versionField);
$versionColumn = $this->_class->getQuotedColumnName($versionField, $this->_platform);
if ($versionFieldType == Type::INTEGER) {
$set[] = $versionColumn . ' = ' . $versionColumn . ' + 1';
} else if ($versionFieldType == Type::DATETIME) {
$set[] = $versionColumn . ' = CURRENT_TIMESTAMP';
}
$where[] = $versionColumn;
$params[] = $this->_class->reflFields[$versionField]->getValue($entity);
$types[] = $this->_class->fieldMappings[$versionField]['type'];
}

$sql = "UPDATE $tableName SET " . implode(', ', $set)
. ' WHERE ' . implode(' = ? AND ', $where) . ' = ?';

$result = $this->_conn->executeUpdate($sql, $params, $types);

if ($this->_class->isVersioned && ! $result) {
throw OptimisticLockException::lockFailed($entity);
}
}
}

 

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}