lorenzo/row-locker

Plugin to lock database rows using the CakePHP ORM

Installs: 9 220

Dependents: 0

Suggesters: 0

Security: 0

Stars: 19

Watchers: 2

Forks: 7

Open Issues: 1

Type:cakephp-plugin

2.0.0 2020-02-18 14:01 UTC

This package is auto-updated.

Last update: 2022-01-19 12:23:16 UTC


README

This plugin offers a simple implementation of row locking by storing a timestamp in a field of the row and the name of the lock owner.

Row locking can be useful in CMS-like systems where many people try to change the same record at the same time. By locking the row you can prevent or alert the users from possible data overwrite.

Installation

You can install this plugin into your CakePHP application using composer.

composer require lorenzo/row-locker

Note: Above will install package compactible with CakePHP4. Please refer to Versions section to install package with CakePHP3.

And then enable the plugin:

bin/cake plugin load RowLocker

Configuration

Any table to which you wish to apply RowLocker needs to have the following columns:

  • locked_time: DATETIME
  • locked_by (optional) Can be of any type that identify your users (INT, VARCHAR, UUID...)
  • locked_session (optional) VARCHAR(100) Used for debugging purposes

Usage

To use RowLocker you first need to add the LockableInterface and LockableTrait to your entity:

use RowLocker\LockableInterface;
use RowLocker\LockableTrait;
...

class Article extends Entity implements LockableInterface
{
    use LockableTrait;

    ...
}

Finally, add the behavior to your Table class:

class ArticlesTable extends Table
{
    public function initialize()
    {
        ...
        $this->addBehavior('RowLocker.RowLocker');
    }
}

Locking Rows

To lock any row first load it and the call lock() on it. The lock will last for 5 minutes:

$article = $articlesTable->get($id);
$article->lock($userId, $sessionId); // both arguments are actaully optional
$articlesTable->save($article);

RowLocker provides a shortcut for doing the above for one or many rows, by using the autoLock finder:

$article = $articlesTable
    ->findById($id)
    ->find('autoLock', ['lockingUser' => $userId, 'lockingSession' => $sessionId])
    ->firstOrFail(); // This locks the row

$article->isLocked(); // return true

Unlocking a Row

Just call unlock() in the entity:

$article->unlock();
$articlesTable->save($article);

Finding Unlocked Rows

In order to find unlocked rows (or with locks owned by the same user), use the unlocked finder:

$firstUnlocked = $articlesTable
    ->find('unlocked', ['lockingUser' => $userId])
    ->firstOrFail();

Safely Locking Rows

In systems with high concurrency (many users trying to get a lock of the same row), it is highly recommended to use the provided lockingMonitor() function:

$safeLocker = $articlesTable->lockingMonitor();
// Safely lock the row
$safeLocker(function () use ($id, $userId, $sessionId) {
    $article = $articlesTable
        ->findById($id)
        ->find('autoLock', ['lockingUser' => $userId, 'lockingSession' => $sessionId])
        ->firstOrFail();
});

What the locking monitor does is running the inner callable inside a SERIALIZABLE transaction.

Versions

RowLocker has several releases, each compatible with different releases of CakePHP. Use the appropriate version by downloading a tag, or checking out the correct branch.

  • 1.x tags are compatible with CakePHP 3.x and greater.
  • 2.x tags is compatible with CakePHP 4.0.x and is stable to use.