/ * @todo consider adding callbacks for batch operations (e.g. * preCommit, postCommit, commitError, revertBatch, etc.) */ class P4Cms_Record_Adapter { const COMMIT_THROW_CONFLICT = "throw"; protected $_basePath = null; protected $_connection = null; protected $_batchId = null; protected $_properties = array(); /** * Get the connection object to use to communicate with Perforce. * * @return P4_Connection_Abstract the p4 connection to use. * @throws P4Cms_Record_Exception if no valid connection is set. */ public function getConnection() { if (!$this->_connection instanceof P4_Connection_Abstract) { throw new P4Cms_Record_Exception( "Cannot get connection. No valid p4 connection has been set." ); } return $this->_connection; } /** * Set the Perforce connection to use to communicate with the * Perforce backend. * * @param P4_Connection_Abstract $p4 the p4 connection to use. * @return P4Cms_Record_Adapter provides fluent interface. * @throws P4Cms_Record_Exception if the connection object is invalid. */ public function setConnection($p4) { if (!$p4 instanceof P4_Connection_Abstract) { throw new P4Cms_Record_Exception( "Cannot set connection. The given argument is not a valid p4 connection." ); } $this->_connection = $p4; return $this; } /** * Get the base path in Perforce under which records should be * read from and written to. * * @return string the record storage base path. * @throws P4Cms_Record_Exception if no base path is set. */ public function getBasePath() { if (!is_string($this->_basePath) || !strlen($this->_basePath)) { throw new P4Cms_Record_Exception( "Cannot get base path. No base path is set." ); } return $this->_basePath; } /** * Set the base path in Perforce under which records should be * read from and written to. * * @param string $path the record storage base path. * @return P4Cms_Record_Adapter provides fluent interface. * @throws P4Cms_Record_Exception if the base path is not a valid string. */ public function setBasePath($path) { if (!is_string($path) || !strlen($path)) { throw new P4Cms_Record_Exception( "Cannot set base path. Given path is not a valid string." ); } $this->_basePath = $path; return $this; } /** * Check if adapter has a particular property. * * @param string $name the property name to check for the existence of * @return boolean true if the adapter has the named property, false otherwise. */ public function hasProperty($name) { return array_key_exists($name, $this->_properties); } /** * Get a particular property value of this adapter. * * @param string $name name of the property to get the value of * @return mixed the value of the property name * @throws P4Cms_Record_Exception if the property name does not exist */ public function getProperty($name) { // return property value if it was set, otherwise throw an exception if ($this->hasProperty($name)) { return $this->_properties[$name]; } throw new P4Cms_Record_Exception( "Cannot find adapter property '$name'. Property was not set." ); } /** * Get all properties of this adapter. * * @return array all properties set to this adapter */ public function getProperties() { return $this->_properties; } /** * Set a particular property of this adapter. * * @param string $name name of the property to set the value of * @param mixed $value value to set * @return P4Cms_Record_Adapter provides a fluent interface */ public function setProperty($name, $value) { $this->_properties[$name] = $value; return $this; } /** * Set adapter properties. * * @param array $properties array with properties to set * @return P4Cms_Record_Adapter provides fluent interface */ public function setProperties(array $properties) { $this->_properties = $properties; return $this; } /** * Start a batch. Changes made to records will be * placed in a numbered pending change and will not be * submitted until the batch is committed. * * Batches cannot be nested. Attempting to begin a * batch while in a batch will result in an * exception. * * @param string $description required - a description of the batch. * @return P4Cms_Record_Adapter provides fluent interface. * @throws P4Cms_Record_Exception if already in a batch. * @todo automatically aggregate descriptions when in batches. */ public function beginBatch($description) { if ($this->inBatch()) { throw new P4Cms_Record_Exception( "Cannot begin batch. Already in a batch." ); } // create a new pending change. $change = new P4_Change($this->getConnection()); $change->setDescription($description)->save(); return $this->_batchId = $change->getId(); } /** * Commit the batch. Submits the pending change * corresponding to the batch id. * * @param string $description optional - a final description of the batch. * @param null|string|array $options optional - passing the SAVE_THROW_CONFLICTS * flag will cause exceptions on conflict; default * behaviour is to crush any conflicts. * @return P4Cms_Record_Adapter provides fluent interface. * @throws P4Cms_Record_Exception if not in a batch. */ public function commitBatch($description = null, $options = null) { if (!$this->inBatch()) { throw new P4Cms_Record_Exception( "Cannot commit batch. Not in a batch." ); } // submit the change identified by batch id. $change = P4_Change::fetch( $this->getBatchId(), $this->getConnection() ); try { // default option is to 'accept yours' but we switch to // null if SAVE_THROW_CONFLICTS flag is passed. $resolveFlag = P4_Change::RESOLVE_ACCEPT_YOURS; if (in_array(static::COMMIT_THROW_CONFLICT, (array)$options)) { $resolveFlag = null; } $change->submit($description, $resolveFlag); } catch (P4_Connection_CommandException $e) { // ignore exception if change is empty - otherwise rethrow. if (count($change->getFiles()) == 0) { $change->delete(); } else { throw $e; } } // clear the batch id. $this->_batchId = null; return $this; } /** * Reverts all files in the pending change corresponding to * the batch id. * * @return P4Cms_Record_Adapter provides fluent interface. * @throws P4Cms_Record_Exception if not in a batch. * @todo automatically revert batches when unhandled exceptions/errors occur. */ public function revertBatch() { if (!$this->inBatch()) { throw new P4Cms_Record_Exception( "Cannot revert batch. Not in a batch." ); } // revert the change identified by batch id. $change = P4_Change::fetch( $this->getBatchId(), $this->getConnection() ); $change->revert() ->delete(); // clear the batch id. $this->_batchId = null; return $this; } /** * Get the id of the current batch. The batch id * corresponds to a pending changelist in Perforce. * * @return int the batch id (pending change number). * @throws P4Cms_Record_Exception if not in a batch. */ public function getBatchId() { if (!$this->inBatch()) { throw new P4Cms_Record_Exception( "Cannot get batch id. Not in a batch." ); } return (int) $this->_batchId; } /** * Determine if we are currently in a batch on this * storage adapter. * * @return bool true if we are in a batch. */ public function inBatch() { $id = $this->_batchId; return (int) $id > 0; } /** * Change the description of the current batch * * @param string $description the description of the current batch. * @return P4Cms_Record_Adapter provides fluent interface. */ public function setBatchDescription($description) { $change = P4_Change::fetch( $this->getBatchId(), $this->getConnection() ); $change->setDescription($description) ->save(); return $this; } }