Source for file Pessimistic.php

Documentation is available at Pessimistic.php

  1. <?php
  2. /*
  3.  *  $Id: Pessimistic.php 1468 2007-05-24 16:54:42Z zYne $
  4.  *
  5.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16.  *
  17.  * This software consists of voluntary contributions made by many individuals
  18.  * and is licensed under the LGPL. For more information, see
  19.  * <http://www.phpdoctrine.com>.
  20.  */
  21. /**
  22.  * Offline locking of records comes in handy where you need to make sure that
  23.  * a time-consuming task on a record or many records, which is spread over several
  24.  * page requests can't be interfered by other users.
  25.  *
  26.  * @link        www.phpdoctrine.com
  27.  * @author      Roman Borschel <roman@code-factory.org>
  28.  * @author      Pierre Minnieur <pm@pierre-minnieur.de>
  29.  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
  30.  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
  31.  * @since       1.0
  32.  * @package     Doctrine
  33.  * @category    Object Relational Mapping
  34.  * @version     $Revision: 1468 $
  35.  */
  36. {
  37.     /**
  38.      * The conn that is used by the locking manager
  39.      *
  40.      * @var Doctrine_Connection object
  41.      */
  42.     private $conn;
  43.     /**
  44.      * The database table name for the lock tracking
  45.      */
  46.     private $_lockTable = 'doctrine_lock_tracking';
  47.  
  48.     /**
  49.      * Constructs a new locking manager object
  50.      *
  51.      * When the CREATE_TABLES attribute of the connection on which the manager
  52.      * is supposed to work on is set to true, the locking table is created.
  53.      *
  54.      * @param Doctrine_Connection $conn The database connection to use
  55.      */
  56.     public function __construct(Doctrine_Connection $conn)
  57.     {
  58.         $this->conn = $conn;
  59.  
  60.         if ($this->conn->getAttribute(Doctrine::ATTR_EXPORTDoctrine::EXPORT_TABLES{
  61.             $columns array();
  62.             $columns['object_type']        array('type'    => 'string',
  63.                                                    'length'  => 50,
  64.                                                    'notnull' => true,
  65.                                                    'primary' => true);
  66.  
  67.             $columns['object_key']         array('type'    => 'string',
  68.                                                    'length'  => 250,
  69.                                                    'notnull' => true,
  70.                                                    'primary' => true);
  71.  
  72.             $columns['user_ident']         array('type'    => 'string',
  73.                                                    'length'  => 50,
  74.                                                    'notnull' => true);
  75.  
  76.             $columns['timestamp_obtained'array('type'    => 'integer',
  77.                                                    'length'  => 10,
  78.                                                    'notnull' => true);
  79.  
  80.             $options array('primary' => array('object_type''object_key'));
  81.             try {
  82.                 $this->conn->export->createTable($this->_lockTable$columns$options);
  83.             catch(Exception $e{
  84.  
  85.             }
  86.         }
  87.     }
  88.  
  89.     /**
  90.      * Obtains a lock on a {@link Doctrine_Record}
  91.      *
  92.      * @param  Doctrine_Record $record     The record that has to be locked
  93.      * @param  mixed           $userIdent  A unique identifier of the locking user
  94.      * @return boolean  TRUE if the locking was successful, FALSE if another user
  95.      *                   holds a lock on this record
  96.      * @throws Doctrine_Locking_Exception  If the locking failed due to database errors
  97.      */
  98.     public function getLock(Doctrine_Record $record$userIdent)
  99.     {
  100.         $objectType $record->getTable()->getComponentName();
  101.         $key        $record->obtainIdentifier();
  102.  
  103.         $gotLock false;
  104.         $time time();
  105.  
  106.         if (is_array($key)) {
  107.             // Composite key
  108.             $key implode('|'$key);
  109.         }
  110.  
  111.         try {
  112.             $dbh $this->conn->getDbh();
  113.             $dbh->beginTransaction();
  114.  
  115.             $stmt $dbh->prepare('INSERT INTO ' $this->_lockTable
  116.                                   . ' (object_type, object_key, user_ident, timestamp_obtained)'
  117.                                   . ' VALUES (:object_type, :object_key, :user_ident, :ts_obtained)');
  118.  
  119.             $stmt->bindParam(':object_type'$objectType);
  120.             $stmt->bindParam(':object_key'$key);
  121.             $stmt->bindParam(':user_ident'$userIdent);
  122.             $stmt->bindParam(':ts_obtained'$time);
  123.  
  124.             try {
  125.                 $stmt->execute();
  126.                 $gotLock true;
  127.  
  128.             // we catch an Exception here instead of PDOException since we might also be catching Doctrine_Exception
  129.             catch(Exception $pkviolation{
  130.                 // PK violation occured => existing lock!
  131.             }
  132.  
  133.             if $gotLock{
  134.                 $lockingUserIdent $this->_getLockingUserIdent($objectType$key);
  135.                 if ($lockingUserIdent !== null && $lockingUserIdent == $userIdent{
  136.                     $gotLock true// The requesting user already has a lock
  137.                     // Update timestamp
  138.                     $stmt $dbh->prepare('UPDATE ' $this->_lockTable 
  139.                                           . ' SET timestamp_obtained = :ts'
  140.                                           . ' WHERE object_type = :object_type AND'
  141.                                           . ' object_key  = :object_key  AND'
  142.                                           . ' user_ident  = :user_ident');
  143.                     $stmt->bindParam(':ts'$time);
  144.                     $stmt->bindParam(':object_type'$objectType);
  145.                     $stmt->bindParam(':object_key'$key);
  146.                     $stmt->bindParam(':user_ident'$lockingUserIdent);
  147.                     $stmt->execute();
  148.                 }
  149.             }
  150.             $dbh->commit();
  151.         catch (Exception $pdoe{
  152.             $dbh->rollback();
  153.             throw new Doctrine_Locking_Exception($pdoe->getMessage());
  154.         }
  155.  
  156.         return $gotLock;
  157.     }
  158.  
  159.     /**
  160.      * Releases a lock on a {@link Doctrine_Record}
  161.      *
  162.      * @param  Doctrine_Record $record    The record for which the lock has to be released
  163.      * @param  mixed           $userIdent The unique identifier of the locking user
  164.      * @return boolean  TRUE if a lock was released, FALSE if no lock was released
  165.      * @throws Doctrine_Locking_Exception If the release procedure failed due to database errors
  166.      */
  167.     public function releaseLock(Doctrine_Record $record$userIdent)
  168.     {
  169.         $objectType $record->getTable()->getComponentName();
  170.         $key        $record->obtainIdentifier();
  171.  
  172.         if (is_array($key)) {
  173.             // Composite key
  174.             $key implode('|'$key);
  175.         }
  176.  
  177.         try {
  178.             $dbh $this->conn->getDbh();
  179.             $stmt $dbh->prepare("DELETE FROM $this->_lockTable WHERE
  180.                                         object_type = :object_type AND
  181.                                         object_key  = :object_key  AND
  182.                                         user_ident  = :user_ident");
  183.             $stmt->bindParam(':object_type'$objectType);
  184.             $stmt->bindParam(':object_key'$key);
  185.             $stmt->bindParam(':user_ident'$userIdent);
  186.             $stmt->execute();
  187.  
  188.             $count $stmt->rowCount();
  189.  
  190.             return ($count 0);
  191.         catch (PDOException $pdoe{
  192.             throw new Doctrine_Locking_Exception($pdoe->getMessage());
  193.         }
  194.     }
  195.  
  196.     /**
  197.      * Gets the unique user identifier of a lock
  198.      *
  199.      * @param  string $objectType  The type of the object (component name)
  200.      * @param  mixed  $key         The unique key of the object
  201.      * @return mixed  The unique user identifier for the specified lock
  202.      * @throws Doctrine_Locking_Exception If the query failed due to database errors
  203.      */
  204.     private function _getLockingUserIdent($objectType$key)
  205.     {
  206.         if (is_array($key)) {
  207.             // Composite key
  208.             $key implode('|'$key);
  209.         }
  210.  
  211.         try {
  212.             $dbh $this->conn->getDbh();
  213.             $stmt $dbh->prepare('SELECT user_ident FROM ' $this->_lockTable
  214.                                   . ' WHERE object_type = :object_type AND object_key = :object_key');
  215.             $stmt->bindParam(':object_type'$objectType);
  216.             $stmt->bindParam(':object_key'$key);
  217.             $success $stmt->execute();
  218.  
  219.             if (!$success{
  220.                 throw new Doctrine_Locking_Exception("Failed to determine locking user");
  221.             }
  222.  
  223.             $userIdent $stmt->fetchColumn();
  224.         catch (PDOException $pdoe{
  225.             throw new Doctrine_Locking_Exception($pdoe->getMessage());
  226.         }
  227.  
  228.         return $userIdent;
  229.     }
  230.  
  231.     /**
  232.      * Gets the identifier that identifies the owner of the lock on the given
  233.      * record.
  234.      *
  235.      * @param Doctrine_Record $lockedRecord  The record.
  236.      * @return mixed The unique user identifier that identifies the owner of the lock.
  237.      */
  238.     public function getLockOwner($lockedRecord)
  239.     {
  240.         $objectType $lockedRecord->getTable()->getComponentName();
  241.         $key        $lockedRecord->obtainIdentifier();
  242.         return $this->_getLockingUserIdent($objectType$key);
  243.     }
  244.  
  245.     /**
  246.      * Releases locks older than a defined amount of seconds
  247.      *
  248.      * When called without parameters all locks older than 15 minutes are released.
  249.      *
  250.      * @param  integer $age  The maximum valid age of locks in seconds
  251.      * @param  string  $objectType  The type of the object (component name)
  252.      * @param  mixed   $userIdent The unique identifier of the locking user
  253.      * @return integer The number of locks that have been released
  254.      * @throws Doctrine_Locking_Exception If the release process failed due to database errors
  255.      */
  256.     public function releaseAgedLocks($age 900$objectType null$userIdent null)
  257.     {
  258.         $age time($age;
  259.  
  260.         try {
  261.             $dbh $this->conn->getDbh();
  262.             $stmt $dbh->prepare('DELETE FROM ' $this->_lockTable . ' WHERE timestamp_obtained < :age');
  263.             $stmt->bindParam(':age'$age);
  264.             $query 'DELETE FROM ' $this->_lockTable . ' WHERE timestamp_obtained < :age';
  265.             if ($objectType{
  266.                 $query .= ' AND object_type = :object_type';
  267.             }
  268.             if ($userIdent{
  269.                 $query .= ' AND user_ident = :user_ident';
  270.             }
  271.             $stmt $dbh->prepare($query);
  272.             $stmt->bindParam(':age'$age);
  273.             if ($objectType{
  274.                 $stmt->bindParam(':object_type'$objectType);
  275.             }
  276.             if ($userIdent{
  277.                 $stmt->bindParam(':user_ident'$userIdent);
  278.             }
  279.             $stmt->execute();
  280.  
  281.             $count $stmt->rowCount();
  282.  
  283.             return $count;
  284.         catch (PDOException $pdoe{
  285.             throw new Doctrine_Locking_Exception($pdoe->getMessage());
  286.         }
  287.     }
  288.  
  289. }