<?php
/**
* Abstracts operations against Perforce branch specs.
*
* @copyright 2011 Perforce Software. All rights reserved.
* @license Please see LICENSE.txt in top-level folder of this distribution.
* @version <release>/<patch>
* @todo Add support for the following commands:
* integrate
*/
namespace P4\Spec;
use P4\Validate;
use P4\Connection\ConnectionInterface;
class Branch extends PluralAbstract
{
const SPEC_TYPE = 'branch';
const ID_FIELD = 'Branch';
const FETCH_BY_NAME = 'name';
const FETCH_BY_OWNER = 'owner';
protected $fields = array(
'Update' => array(
'accessor' => 'getUpdateDateTime'
),
'Access' => array(
'accessor' => 'getAccessDateTime'
),
'Owner' => array(
'accessor' => 'getOwner',
'mutator' => 'setOwner'
),
'Description' => array(
'accessor' => 'getDescription',
'mutator' => 'setDescription'
),
'Options' => array(
'accessor' => 'getOptions',
'mutator' => 'setOptions'
),
'View' => array(
'accessor' => 'getView',
'mutator' => 'setView'
)
);
/**
* Get all Branches from Perforce. Adds filtering options.
*
* @param array $options optional - array of options to augment fetch behavior.
* supported options are:
*
* FETCH_MAXIMUM - set to integer value to limit to the
* first 'max' number of entries.
* FETCH_BY_NAME - set to branch name pattern (e.g. 'bran*').
* FETCH_BY_OWNER - set to owner's username (e.g. 'jdoe').
*
* @param ConnectionInterface $connection optional - a specific connection to use.
* @return P4\Model\Fielded\Iterator all records of this type.
*/
public static function fetchAll($options = array(), ConnectionInterface $connection = null)
{
// simply return parent - method exists to document options.
return parent::fetchAll($options, $connection);
}
/**
* Determine if the given branch id exists.
*
* @param string $id the id to check for.
* @param ConnectionInterface $connection optional - a specific connection to use.
* @return bool true if the given id matches an existing branch.
*/
public static function exists($id, ConnectionInterface $connection = null)
{
// check id for valid format
if (!static::isValidId($id)) {
return false;
}
$branches = static::fetchAll(
array(
static::FETCH_BY_NAME => $id,
static::FETCH_MAXIMUM => 1
),
$connection
);
return (bool) count($branches);
}
/**
* Get the last update time for this branch spec.
* This value is read only, no setUpdateTime function is provided.
*
* If this is a brand new spec, null will be returned in lieu of a time.
*
* @return string|null Date/Time of last update, formatted "2009/11/23 12:57:06" or null
*/
public function getUpdateDateTime()
{
return $this->getRawValue('Update');
}
/**
* Get the last access time for this branch spec.
* This value is read only, no setAccessTime function is provided.
*
* If this is a brand new spec, null will be returned in lieu of a time.
*
* @return string|null Date/Time of last access, formatted "2009/11/23 12:57:06" or null
*/
public function getAccessDateTime()
{
return $this->getRawValue('Access');
}
/**
* Get the owner of this branch.
*
* @return string|null User who owns this record.
*/
public function getOwner()
{
return $this->getRawValue('Owner');
}
/**
* Set the owner of this branch to passed value.
*
* @param string|null $owner A string containing username or null for none
* @return Branch provides a fluent interface.
* @throws \InvalidArgumentException Owner is incorrect type.
*/
public function setOwner($owner)
{
if (!is_string($owner) && !is_null($owner)) {
throw new \InvalidArgumentException('Owner must be a string or null.');
}
return $this->setRawValue('Owner', $owner);
}
/**
* Get the description for this branch.
*
* @return string description for this branch.
*/
public function getDescription()
{
return $this->getRawValue('Description');
}
/**
* Set a description for this branch.
*
* @param string|null $description description for this branch.
* @return Branch provides a fluent interface.
* @throws \InvalidArgumentException Description is incorrect type.
*/
public function setDescription($description)
{
if (!is_string($description) && !is_null($description)) {
throw new \InvalidArgumentException('Description must be a string or null.');
}
return $this->setRawValue('Description', $description);
}
/**
* Get options for this branch.
*
* @return string options which are set on this branch ('locked' or 'unlocked').
*/
public function getOptions()
{
return $this->getRawValue('Options');
}
/**
* Set the options for this branch. See getOptions for expected values.
*
* @param string|null $options options to set on this branch.
* @return Branch provides a fluent interface.
* @throws \InvalidArgumentException Options are incorrect type.
*/
public function setOptions($options)
{
if (!is_string($options) && !is_null($options)) {
throw new \InvalidArgumentException('Options must be a string or null.');
}
return $this->setRawValue('Options', $options);
}
/**
* Get the view for this branch.
* View entries will be returned as an array with 'source' and 'target' entries, e.g.:
* array (
* 0 => array (
* 'source' => '//depot/branchA/with space/...',
* 'target' => '//depot/branchB/with space/...'
* )
* )
*
* @return array list view entries for this branch.
*/
public function getView()
{
// The raw view data is formatted as:
// array (
// 0 => '"//depot/example/with space/..." //depot/example/nospace/...',
// )
//
// We split this into 'source' and 'target' components via the str_getcsv function
// and key the two resulting entries as 'source' and 'target'
$view = array();
// The ?: translates empty views into an empty array
foreach ($this->getRawValue('View') ?: array() as $entry) {
$entry = str_getcsv($entry, ' ');
$view[] = array_combine(array('source','target'), $entry);
}
return $view;
}
/**
* Set the view for this branch.
* View is passed as an array of view entries. Each view entry can be an array with
* 'source' and 'target' entries or a raw string.
*
* @param array $view View entries, formatted into source/target sub-arrays.
* @return Branch provides a fluent interface.
* @throws \InvalidArgumentException View array, or a view entry, is incorrect type.
*/
public function setView($view)
{
if (!is_array($view)) {
throw new \InvalidArgumentException('View must be passed as array.');
}
// The View array contains either:
// - Child arrays keyed on source/target which we glue together
// - Raw strings which we simply leave as is
// The below foreach run will normalize the whole thing for storage
$parsedView = array();
foreach ($view as $entry) {
if (is_array($entry) &&
isset($entry['source'], $entry['target']) &&
is_string($entry['source']) &&
is_string($entry['target'])) {
$entry = '"'. $entry['source'] .'" "'. $entry['target'] .'"';
}
if (!is_string($entry)) {
throw new \InvalidArgumentException(
"Each view entry must be a 'source' and 'target' array or a string."
);
}
$validate = str_getcsv($entry, ' ');
if (count($validate) != 2 || trim($validate[0]) === '' || trim($validate[1]) === '') {
throw new \InvalidArgumentException(
"Each view entry must contain two depot paths, no more, no less."
);
}
$parsedView[] = $entry;
};
return $this->setRawValue('View', $parsedView);
}
/**
* Add a view mapping to this Branch.
*
* @param string $source the source half of the view mapping.
* @param string $target the target half of the view mapping.
* @return Branch provides a fluent interface.
*/
public function addView($source, $target)
{
$mappings = $this->getView();
$mappings[] = array("source" => $source, "target" => $target);
return $this->setView($mappings);
}
/**
* Produce set of flags for the spec list command, given fetch all options array.
* Extends parent to add support for filter option.
*
* @param array $options array of options to augment fetch behavior.
* see fetchAll for documented options.
* @return array set of flags suitable for passing to spec list command.
*/
protected static function getFetchAllFlags($options)
{
$flags = parent::getFetchAllFlags($options);
if (isset($options[static::FETCH_BY_NAME])) {
$name = $options[static::FETCH_BY_NAME];
if (!is_string($name) || trim($name) === "") {
throw new \InvalidArgumentException(
'Filter by Name expects a non-empty string as input'
);
}
$flags[] = '-e';
$flags[] = $name;
}
if (isset($options[static::FETCH_BY_OWNER])) {
$owner = $options[static::FETCH_BY_OWNER];
// We allow empty values as this returns branches with no owner
if (!is_string($owner) || trim($owner) === "") {
throw new \InvalidArgumentException(
'Filter by Owner expects a non-empty string as input'
);
}
$flags[] = '-u';
$flags[] = $owner;
}
return $flags;
}
/**
* Check if the given id is in a valid format for this spec type.
*
* @param string $id the id to check
* @return bool true if id is valid, false otherwise
*/
protected static function isValidId($id)
{
$validator = new Validate\SpecName;
$validator->allowRelative(true);
$validator->allowPercent(false);
$validator->allowCommas(false);
$validator->allowSlashes(true);
return $validator->isValid($id);
}
/**
* Get the fetch all command, generally a plural version of the spec type.
*
* @return string Perforce command to use for fetchAll
*/
protected static function getFetchAllCommand()
{
// Branch is a special case; over-ridden to add 'es' instead of 's' to spec type.
return static::SPEC_TYPE . "es";
}
}