Perforce Chronicle 2012.2/486814
API Documentation
|
Provides persistent storage of data models in Perforce. More...
Public Member Functions | |
delete ($description=null) | |
Delete this record. | |
getFieldMetadata ($field) | |
Get metadata for the given field. | |
getFields () | |
Get all of the model field names. | |
getId () | |
Get the id of this record. | |
isDeleted () | |
Test if this record is deleted in perforce. | |
isValidId ($id) | |
Determine if id is valid identifier for this record. | |
save ($description=null, $options=null) | |
Save this record. | |
setAdapter (P4Cms_Record_Adapter $adapter) | |
Override parent to clear associated p4 file if adapter has changed to ensure the file will get the connection from the new adapter. | |
setFieldMetadata ($field, array $data=null) | |
Set metadata for the given field. | |
setId ($id) | |
Set the id of this record. | |
setValue ($field, $value) | |
Set a particular field value. | |
setValues ($values, $filter=false) | |
Set all of the model's values at once. | |
toP4File ($reference=false) | |
Provides access to a copy of the p4_file object which is underlying the current record instance. | |
Static Public Member Functions | |
static | count (P4Cms_Record_Query $query=null, P4Cms_Record_Adapter $adapter=null) |
Count all records matching the given query. | |
static | create ($values=null, P4Cms_Record_Adapter $adapter=null) |
Create a new record instance, using optional field values, in a chainable fashion. | |
static | depotFileToId ($depotFile, P4Cms_Record_Adapter $adapter=null) |
Given a record filespec in depotFile syntax, determine the id. | |
static | exists ($id, $query=null, P4Cms_Record_Adapter $adapter=null) |
Check if a record with the given id exists. | |
static | fetch ($id, $query=null, P4Cms_Record_Adapter $adapter=null) |
Get a specific record by id. | |
static | fetchAll ($query=null, P4Cms_Record_Adapter $adapter=null) |
Get all records under the record storage path. | |
static | fromP4File ($file, $options=null, P4Cms_Record_Adapter $adapter=null) |
Given a p4 file instance, produce a record instance with id adapter and associated p4 file object all set appropriately. | |
static | getDepotStoragePath (P4Cms_Record_Adapter $adapter=null) |
Get the depot-syntax form of the perforce path used for the storage of this class of records. | |
static | getFileContentField () |
Get the name of the field that is mapped to the file contents. | |
static | getIdField () |
Return name of the id field. | |
static | getStoragePath (P4Cms_Record_Adapter $adapter=null) |
Get the Perforce path used for the storage of this class of records. | |
static | hasFileContentField () |
Determine if this record class has a field mapped to the file contents. | |
static | idToFilespec ($id, P4Cms_Record_Adapter $adapter=null) |
Given a record id, determine the corresponding filespec. | |
static | remove ($id, P4Cms_Record_Adapter $adapter=null) |
Remove a record from storage. | |
static | store ($values=array(), P4Cms_Record_Adapter $adapter=null) |
Store a record. | |
Public Attributes | |
const | ENCODING_FORMAT_JSON = "json" |
const | ENCODING_METADATA_KEY = "_encoding" |
const | FROM_FILE_IMPORT = 'import' |
const | SAVE_THROW_CONFLICT = "throw" |
Protected Member Functions | |
_decodeFieldValue ($field, $value) | |
Decode field's value if it is encoded (checks field metadata). | |
_decodeId ($id) | |
Decode stored id (reverse bin2hex). | |
_decodeMetadata ($data) | |
Decode metadata (presumably from storage). | |
_deferPopulate () | |
Schedule populate to run when data is requested (lazy-load). | |
_encodeFieldValue ($field, $value) | |
Encode field's value as JSON if its not string or numeric. | |
_encodeId ($id) | |
Encode id for storage (via bin2hex). | |
_encodeMetadata ($data) | |
Encode metadata for storage (using JSON). | |
_generateSubmitDescription () | |
Generate a save description for this record. | |
_getFieldMetadata ($field) | |
Get metadata for the given field - doesn't populate or check field existance. | |
_getP4File () | |
Get the P4 File object that corresponds to this record. | |
_getValue ($field) | |
Overrides parent to populate the record first. | |
_populate ($excludeFile=false) | |
Get the values for this record from Perforce and set them in the instance. | |
_setP4File ($file) | |
Set the corresponding P4 File object instance. | |
Static Protected Member Functions | |
static | _normalizeQuery ($query) |
Queries arguments (e.g. | |
Protected Attributes | |
$_id = null | |
$_metadata = array() | |
$_needsFilePopulate = false | |
$_needsPopulate = false | |
$_p4File = null | |
Static Protected Attributes | |
static | $_encodeIds = false |
Optionally, bin2hex encode identifiers when converting to/from depot filespecs to permit non-standard characters. | |
static | $_fields = array() |
Specifies the array of fields that the current Record class wishes to use. | |
static | $_fileContentField = null |
Specifies the name of the record field which will be persisted in the file used to store the records. | |
static | $_hasValidFields = null |
static | $_idField = 'id' |
All records should have an id field. | |
static | $_storageSubPath = null |
Specifies the sub-path to use for storage of records. | |
static | $_whereCache = array() |
Provides persistent storage of data models in Perforce.
Each record corresponds to a file in Perforce. Each record may contain properties that will be stored as attributes on the corresponding file (if sub-classed, a single property may be selected for storage in the file).
Records are schemaless. Records of the same kind are not obligated to have the same fields. However, the record class may be sub-classed to define fields.
Each record has an id that uniquely identifies the record in the record storage path. The storage base path is provided by the record storage adapter and may be narrowed (if sub- classed) by specifying a storage sub-path.
If no id is specified when saving a record a new UUID will be assigned. UUIDs are used instead of incrementing numbers because they avoid collisions when record files are moved or branched in the depot.
A single record can be fetched by its id via the fetch() method. Multiple records can be fetched via fetchAll().
Records can be saved via the save() method and deleted via delete(). Each save() and delete() constitutes a submit in Perforce and produces a new revision of the record.
Field names must be valid as file attribute names. Additionally, field names must not begin with an underscore ('_'). Leading underscore is reserved for field metadata.
P4Cms_Record::_decodeFieldValue | ( | $ | field, |
$ | value | ||
) | [protected] |
Decode field's value if it is encoded (checks field metadata).
string | $field | the field we are decoding |
string | $value | the encoded value. |
{ $metadata = $this->_getFieldMetadata($field); if (strlen($value) && isset($metadata[self::ENCODING_METADATA_KEY]) && self::ENCODING_FORMAT_JSON === $metadata[self::ENCODING_METADATA_KEY] ) { try { return Zend_Json::decode($value); } catch (Exception $e) { P4Cms_Log::logException("Failed to decode field value", $e); } } // convert empty strings to null. // this is done so that null values round-trip correctly // this prevents empty strings from round-tripping, but // that was deemed a reasonable trade-off. if (!strlen($value)) { return null; } return $value; }
P4Cms_Record::_decodeId | ( | $ | id | ) | [protected] |
Decode stored id (reverse bin2hex).
string | $id | the id to decode. |
{ return pack("H*", $id); }
P4Cms_Record::_decodeMetadata | ( | $ | data | ) | [protected] |
Decode metadata (presumably from storage).
string | $data | The data to be decoded. |
{
return strlen($data)
? Zend_Json::decode($data)
: null;
}
P4Cms_Record::_deferPopulate | ( | ) | [protected] |
Schedule populate to run when data is requested (lazy-load).
{ $this->_needsPopulate = true; $this->_needsFilePopulate = true; return $this; }
P4Cms_Record::_encodeFieldValue | ( | $ | field, |
$ | value | ||
) | [protected] |
Encode field's value as JSON if its not string or numeric.
Updates field metadata to record encoding.
string | $field | the field to encode the value for. |
mixed | $value | the value to encode. |
{ $metadata = $this->_getFieldMetadata($field); if (is_numeric($value)) { $value = (string) $value; } if (isset($value) && !is_string($value)) { // json encode $value = Zend_Json::encode($value); $metadata[self::ENCODING_METADATA_KEY] = self::ENCODING_FORMAT_JSON; } else if (array_key_exists(self::ENCODING_METADATA_KEY, $metadata)) { unset($metadata[self::ENCODING_METADATA_KEY]); } $this->setFieldMetadata($field, $metadata); return $value; }
P4Cms_Record::_encodeId | ( | $ | id | ) | [protected] |
Encode id for storage (via bin2hex).
string | $id | the id to encode. |
Reimplemented in Url_Model_Url.
{
return bin2hex($id);
}
P4Cms_Record::_encodeMetadata | ( | $ | data | ) | [protected] |
Encode metadata for storage (using JSON).
mixed | $data | The data to be encoded. |
{
return Zend_Json::encode($data);
}
P4Cms_Record::_generateSubmitDescription | ( | ) | [protected] |
Generate a save description for this record.
{ return static::$_storageSubPath ? "Saved '" . static::$_storageSubPath . "' record." : "Saved record."; }
P4Cms_Record::_getFieldMetadata | ( | $ | field | ) | [protected] |
Get metadata for the given field - doesn't populate or check field existance.
Field metadata is stored in a file attribute named for the field, but with a leading underscore (e.g. '_field-name').
string | $field | the field to get metadata for. |
{ if (array_key_exists($field, $this->_metadata)) { return (array) $this->_metadata[$field]; } else { return array(); } }
P4Cms_Record::_getP4File | ( | ) | [protected] |
Get the P4 File object that corresponds to this record.
{ // create corresponding p4 file instance if necessary. if (!$this->_p4File instanceof P4_File) { $filespec = static::idToFilespec($this->getId(), $this->getAdapter()); $this->_p4File = new P4_File; $this->_p4File->setFilespec($filespec) ->setConnection($this->getAdapter()->getConnection()); } return $this->_p4File; }
P4Cms_Record::_getValue | ( | $ | field | ) | [protected] |
Overrides parent to populate the record first.
Get a raw (but decoded) field value. Does not use custom accessor methods. If idField is specified; will utilize 'getId' function.
string | $field | the name of the field to get the value of. |
P4Cms_Model_Exception | if the field does not exist. |
Reimplemented from P4Cms_Model.
{ $excludeFile = ($field !== static::$_fileContentField); $this->_populate($excludeFile); return parent::_getValue($field); }
static P4Cms_Record::_normalizeQuery | ( | $ | query | ) | [static, protected] |
Queries arguments (e.g.
to fetch/fetchAll) can be given as a query object, an array or null. This helper method normalizes the input to a query object and throws on invalid arguments.
P4Cms_Record_Query | array | null | $query | optional - query options to augment result. * |
InvalidArgumentException | if the query input is not valid. |
{ if (!$query instanceof P4Cms_Record_Query && !is_array($query) && !is_null($query)) { throw new InvalidArgumentException( 'Query must be a P4Cms_Record_Query, array or null' ); } // normalize array input to a query if (is_array($query)) { $query = new P4Cms_Record_Query($query); } // if null query given, make a new one. $query = $query ?: new P4Cms_Record_Query; return $query; }
P4Cms_Record::_populate | ( | $ | excludeFile = false | ) | [protected] |
Get the values for this record from Perforce and set them in the instance.
Won't clobber existing values.
bool | $excludeFile | optional - skip populating file content |
{ // if record has no id and no file, we can't pull from storage. if (!$this->hasId() && !$this->_p4File) { return $this; } if ($this->_needsPopulate) { // clear needsPopulate flag. $this->_needsPopulate = false; // get file attributes from associated p4 file. $file = $this->_getP4File(); try { $attributes = $file->getAttributes(); } catch (P4_File_Exception $e) { // no matching file in storage, nothing to populate from. return $this; } // set field metadata first from file attributes. foreach ($attributes as $key => $value) { if ($key[0] === '_') { $field = substr($key, 1); if (!array_key_exists($field, $this->_metadata)) { try { $this->_metadata[$field] = $this->_decodeMetadata($value); } catch (Exception $e) { // we failed to decode the metadata entry -- we set it to // false to tell save that the attribute should be ignored. $this->_metadata[$field] = false; } } } } // set field values from file attributes. $validator = new P4Cms_Validate_RecordField; foreach ($attributes as $key => $value) { if ($validator->isValid($key)) { if (!array_key_exists($key, $this->_values)) { $this->_values[$key] = $this->_decodeFieldValue($key, $value); } } } } if ($this->_needsFilePopulate && !$excludeFile) { // clear needsPopulate flag. $this->_needsFilePopulate = false; // set file content field if record has one. $fileField = static::$_fileContentField; if (strlen($fileField) && !array_key_exists($fileField, $this->_values)) { $file = $this->_getP4File(); try { if (!$file->isDeleted()) { $contents = $file->getDepotContents(); } else { // if we are deleted, pull the file content from the previous revision $revSpec = '#' . ((int)$file->getStatus('headRev') - 1); $contents = P4_File::fetch( $file->getFilespec(true) . $revSpec, $file->getConnection() )->getDepotContents(); } $this->_values[$fileField] = $this->_decodeFieldValue($fileField, $contents); } catch (P4_File_Exception $e) { // presumably no depot file content to get. } } } return $this; }
P4Cms_Record::_setP4File | ( | $ | file | ) | [protected] |
Set the corresponding P4 File object instance.
Used when fetching records to prime the record object.
Record_Exception | if the file is not a valid P4_File object. |
{ if (!$file instanceof P4_File && !is_null($file)) { throw new P4Cms_Record_Exception( 'Cannot set P4 File. The given file is not a valid P4_File object.' ); } $this->_p4File = $file; return $this; }
static P4Cms_Record::count | ( | P4Cms_Record_Query $ | query = null , |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Count all records matching the given query.
P4Cms_Record_Query | array | null | $query | optional - query options to augment result. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
Reimplemented in P4Cms_Record_PubSubRecord.
{ $query = static::_normalizeQuery($query); // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // convert record query to a p4 file query. $query = $query->toFileQuery(get_called_class(), $adapter); // early exit if no filespecs in query, return zero. if (is_array($query->getFilespecs()) && !count($query->getFilespecs())) { return 0; } // only fetch a single field - use headRev because it's tiny. $query->setLimitFields('headRev'); // fetch count from perforce. return P4_File::count($query, $adapter->getConnection()); }
static P4Cms_Record::create | ( | $ | values = null , |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Create a new record instance, using optional field values, in a chainable fashion.
array | $values | associative array of keyed field values to load into the model. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ return new static($values, $adapter); }
P4Cms_Record::delete | ( | $ | description = null | ) |
Delete this record.
string | $description | optional - a description of the change. |
Reimplemented in P4Cms_Categorization_CategoryAbstract, P4Cms_Content_Type, P4Cms_Content, P4Cms_Record_PubSubRecord, and Url_Model_Url.
{ // if we are in a batch, pend the record to the // changelist identified by the batch id. $adapter = $this->getAdapter(); $change = ($adapter->inBatch()) ? $adapter->getBatchId() : null; // open depot file for delete. $file = $this->_getP4File(); try { $file->delete($change); } catch (P4_File_Exception $e) { // ignore exception if file was open for add - otherwise rethrow. if (!$file->isOpened() || $file->getStatus('action') !== 'add') { throw $e; } } // ensure local file deleted. if (file_exists($file->getLocalFilename())) { $file->deleteLocalFile(); } // if we're not in a batch, submit file to perforce if (!$adapter->inBatch()) { if (!$description) { $description = "Deleted '" . static::$_storageSubPath . "' record."; } $file->submit($description); } return $this; }
static P4Cms_Record::depotFileToId | ( | $ | depotFile, |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Given a record filespec in depotFile syntax, determine the id.
string | $depotFile | a record depotFile. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
Reimplemented in P4Cms_Categorization_CategoryAbstract.
{ // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // strip the depot storage path from the depotFile to produce the id. $depotBasePath = static::getDepotStoragePath($adapter) . '/'; if (strpos($depotFile, $depotBasePath) === 0) { $id = substr($depotFile, strlen($depotBasePath)); // optionally decode stored id. if (static::$_encodeIds) { $id = static::_decodeId($id); } } else { throw new P4Cms_Record_Exception( "Cannot determine record id for a file outside of the record storage path." ); } return $id; }
static P4Cms_Record::exists | ( | $ | id, |
$ | query = null , |
||
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Check if a record with the given id exists.
Query options may, optionally, be passed. Any paths/ids present in the options will ignored.
string | int | $id | the id of the record to fetch. |
P4Cms_Record_Query | array | null | $query | optional - query options to augment result. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ $query = static::_normalizeQuery($query); // if no id given, return false. if (!strlen($id)) { return false; } // clobber any existing IDs with our own and clear any paths on query. $query->setIds(array($id))->setPaths(array()); return static::count($query, $adapter) > 0; }
static P4Cms_Record::fetch | ( | $ | id, |
$ | query = null , |
||
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Get a specific record by id.
A revision specifier may be, optionally, included in the id field. Rev Specifiers will influence the data returned but will not be present in the id of the returned record.
Query options may, optionally, be passed. Any paths/ids present in the options will ignored.
string | int | $id | the id of the record to fetch. |
P4Cms_Record_Query | array | null | $query | optional - query options to augment result. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
P4Cms_Record_NotFoundException | if the requested record can't be found. |
{ $query = static::_normalizeQuery($query); // clobber any existing IDs with our own and clear any paths on options. $query->setIds(array($id))->setPaths(array()); $results = static::fetchAll($query, $adapter); if (!count($results)) { throw new P4Cms_Record_NotFoundException( "Cannot fetch record '$id'. Record does not exist." ); } return $results->first(); }
static P4Cms_Record::fetchAll | ( | $ | query = null , |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Get all records under the record storage path.
Results can be limited by providing a query object or array.
P4Cms_Record_Query | array | null | $query | optional - query options to augment result. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
Reimplemented in P4Cms_Categorization_CategoryAbstract, P4Cms_Menu, P4Cms_Record_PubSubRecord, and Comment_Model_Comment.
{ $query = static::_normalizeQuery($query); // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // convert record query to a p4 file query. $query = $query->toFileQuery(get_called_class(), $adapter); // early exit if no filespecs in query, return empty iterator. if (is_array($query->getFilespecs()) && !count($query->getFilespecs())) { return new P4Cms_Model_Iterator; } // fetch files from perforce. $files = P4_File::fetchAll($query, $adapter->getConnection()); // convert files to records. $records = new P4Cms_Model_Iterator; foreach ($files as $file) { $record = static::fromP4File($file, null, $adapter); $records[$record->getId()] = $record; } return $records; }
static P4Cms_Record::fromP4File | ( | $ | file, |
$ | options = null , |
||
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Given a p4 file instance, produce a record instance with id adapter and associated p4 file object all set appropriately.
Under normal operation a reference is maintained to the given file and the id of the record is derived from the filespec. If the import option is specified, only the values are taken from the file. No reference is maintained and the resulting record will have a null id.
P4_File | $file | a p4 file instance to convert into a record. |
string | array | null | $options | options to influence the operation: FROM_FILE_IMPORT - only the file's values are used, the id is ignored |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ // if no adapter given, use default. $import = in_array(static::FROM_FILE_IMPORT, (array)$options); $adapter = $adapter ?: static::getDefaultAdapter(); $id = $import ? null : static::depotFileToId($file->getDepotFilename(), $adapter); $record = new static(); $record->setId($id) ->setAdapter($adapter) ->_setP4File($file) ->_deferPopulate(); // if we are doing an import force the record to read in // values then clear any reference to the passed file. if ($import) { $record->_populate() ->_setP4File(null); } return $record; }
static P4Cms_Record::getDepotStoragePath | ( | P4Cms_Record_Adapter $ | adapter = null | ) | [static] |
Get the depot-syntax form of the perforce path used for the storage of this class of records.
The path returned by getStoragePath() is in an unknown form. It could be in depot, client or local file-system syntax.
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // get the storage path (in unknown form). $storagePath = static::getStoragePath($adapter); // we cache the depot-syntax version on a per-path, per-adapter basis. // to avoid running 'p4 where' everytime we need to get the depot storage path. if (isset(static::$_whereCache[spl_object_hash($adapter)][$storagePath])) { return static::$_whereCache[spl_object_hash($adapter)][$storagePath]; } // convert to depot-syntax. $result = $adapter->getConnection()->run('where', $storagePath . '/...'); if ($result->hasWarnings()) { throw new P4Cms_Record_Exception( "Cannot get the depot storage path. Storage path is not in client view." ); } $depotPath = substr($result->getData(0, 'depotFile'), 0, -4); // cache per adapter/path. static::$_whereCache[spl_object_hash($adapter)][$storagePath] = $depotPath; return $depotPath; }
P4Cms_Record::getFieldMetadata | ( | $ | field | ) |
Get metadata for the given field.
Field metadata is stored in a file attribute named for the field, but with a leading underscore (e.g. '_field-name').
string | $field | the field to get metadata for. |
P4Cms_Record_Exception | if the field does not exist. |
{ // populate but skip getting the file contents at this point $this->_populate(true); if (!$this->hasField($field)) { throw new P4Cms_Record_Exception( "Cannot get field metadata for a non-existant field." ); } return $this->_getFieldMetadata($field); }
P4Cms_Record::getFields | ( | ) |
Get all of the model field names.
Extends parent to populate first and throw an exception if it encounters any invalid field names.
P4Cms_Record_Exception | if any of the predefined field names are invalid. |
Reimplemented from P4Cms_Model.
Reimplemented in P4Cms_Content.
{ // populate but skip getting the file contents at this point $this->_populate(true); // validate predefined fields on first access. if (static::$_hasValidFields === null) { static::$_hasValidFields = true; $validator = new P4Cms_Validate_RecordField; foreach ($this->getDefinedFields() as $field) { if (!$validator->isValid($field)) { static::$_hasValidFields = false; } } } // if fields are invalid, throw exception. if (static::$_hasValidFields === false) { throw new P4Cms_Record_Exception( "Cannot get fields. Record has one or more fields with invalid names." ); } // let parent do its thing. $fields = parent::getFields(); // ensure file content field is present if defined. if (!empty(static::$_fileContentField) && !in_array(static::$_fileContentField, $fields)) { $fields[] = static::$_fileContentField; } return $fields; }
static P4Cms_Record::getFileContentField | ( | ) | [static] |
Get the name of the field that is mapped to the file contents.
P4Cms_Record_Exception | if there is no file content field. |
{ if (!static::hasFileContentField()) { throw new P4Cms_Record_Exception( "Cannot get the file content field. No field is mapped to the file." ); } return static::$_fileContentField; }
P4Cms_Record::getId | ( | ) |
Get the id of this record.
Extended to always return a string or null.
Reimplemented from P4Cms_Model.
{ $id = parent::getId(); // cast non-null ids to strings. return $id === null ? null : (string) $id; }
static P4Cms_Record::getIdField | ( | ) | [static] |
static P4Cms_Record::getStoragePath | ( | P4Cms_Record_Adapter $ | adapter = null | ) | [static] |
Get the Perforce path used for the storage of this class of records.
The storage path is a combination of the records path (provided by the record storage adapter) and the sub-path (defined by the record class).
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // normalize the path components. $basePath = rtrim($adapter->getBasePath(), '/'); $subPath = rtrim(static::$_storageSubPath, '/'); // return basePath w. subPath (if set). return strlen($subPath) ? $basePath . '/' . $subPath : $basePath; }
static P4Cms_Record::hasFileContentField | ( | ) | [static] |
Determine if this record class has a field mapped to the file contents.
{
return isset(static::$_fileContentField);
}
static P4Cms_Record::idToFilespec | ( | $ | id, |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Given a record id, determine the corresponding filespec.
string | $id | the record id to get the filespec for. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
Reimplemented in P4Cms_Categorization_CategoryAbstract.
{ // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); // id is required. if (!strlen($id)) { throw new InvalidArgumentException("Cannot get filespec for an empty id."); } // optionally encode id for storage. if (static::$_encodeIds) { $id = static::_encodeId($id); } return static::getStoragePath($adapter) . '/' . $id; }
P4Cms_Record::isDeleted | ( | ) |
Test if this record is deleted in perforce.
{ return $this->getId() && $this->_getP4File()->isDeleted(); }
P4Cms_Record::isValidId | ( | $ | id | ) |
Determine if id is valid identifier for this record.
string | $id | record identifier |
{ $id = static::$_encodeIds ? static::_encodeId($id) : $id; $validator = new P4Cms_Validate_RecordId; return $validator->isValid($id); }
static P4Cms_Record::remove | ( | $ | id, |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Remove a record from storage.
Equivalent to delete but class-based for convenience.
string | $id | the id of the record to remove. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ // if no adapter given, use default. $adapter = $adapter ?: static::getDefaultAdapter(); $record = new static; $record->setId($id) ->setAdapter($adapter) ->delete(); return $record; }
P4Cms_Record::save | ( | $ | description = null , |
$ | options = null |
||
) |
Save this record.
If the record does not have an id, a new UUID will be assigned to identify the record.
string | $description | optional - a description of the change. |
null | string | array | $options | optional - passing the SAVE_THROW_CONFLICTS flag will cause exceptions on conflict; default behaviour is to crush any conflicts. Note this flag has no effect in batches. |
Reimplemented in P4Cms_Content, P4Cms_Record_PubSubRecord, and Url_Model_Url.
{ // if we are in a batch, pend the record to the // changelist identified by the batch id. $adapter = $this->getAdapter(); $change = ($adapter->inBatch()) ? $adapter->getBatchId() : null; // if this record has an id, attempt to flush and edit the file. // if it has no id, generate a new UUID to identify the record. if ($this->getId()) { $file = $this->_getP4File(); try { // if our file isn't deleted simply attempt to sync and edit. // perforce doesn't let you edit a deleted revision (you can't // 'have' a deleted revision) so if it is deleted, sync to the // previous revision and attempt to edit that. if (!$file->isDeleted()) { $file->sync(); $file->edit($change); } else { // if we are deleted, sync to the previous revision // we create a new file object because we want to sync/edit // the previous version without changing this record object's // file instance (which would have negative side-effects). $revSpec = '#' . ((int)$file->getStatus('headRev') - 1); $previousFile = P4_File::fetch( $file->getFilespec(true) . $revSpec, $file->getConnection() ); $previousFile->sync(); // attempt to open for edit. if the file is deleted at the head // revision this will fail and we will open for add later. $previousFile->edit($change); // clear file's status cache so it can be aware of changes made // by previousFile (e.g. 'isOpened' check will be acurate) $file->clearStatusCache(); } } catch (P4_File_Exception $e) { // edit failed, but that's ok - we'll attempt to add below. } catch (P4_Connection_CommandException $e) { // if command failed due to a chmod error, just eat the exception; // file will get created later. normally this problem should not // occur, but if a virtual integrate or copy was performed, it can. if (!stripos($e->getMessage(), "Command failed: chmod: ") === 0) { throw $e; } } } else { $this->setId((string) new P4Cms_Uuid); $file = $this->_getP4File(); } // write file content field to file contents. // if we don't have a file content field we // simply touch the file to ensure it's on disk if (static::hasFileContentField()) { $field = static::getFileContentField(); // we avoid reading the file into memory if possible // but there are situations where we have to: // - if this is an add write the value to persist the default // - if this is an edit the file should already exist but if its missing // make a go of reading its current value and writing it back out // - lastly if we have a value in memory we need to write it to persist it if (!$file->isOpened() || !file_exists($file->getLocalFilename()) || array_key_exists($field, $this->_values) ) { $value = $this->_encodeFieldValue($field, $this->_getValue($field)); $file->setLocalContents($value); } } else { $file->touchLocalFile(); } // if file is not yet opened, add it now - we do this after // the file is written so perforce can detect the file type. if (!$file->isOpened()) { $file->add($change); } // write field values and metadata as file attributes. // we clear any attributes we don't know about (ie. the field was // explicitly unset, or this record was not fetched, but happens // to collide with a file in perforce that has attributes) $clear = array(); $ignore = array(); $attributes = array(); // collect field values to set as attributes. foreach ($this->getFields() as $field) { if ($field != static::$_idField && $field != static::$_fileContentField) { $attributes[$field] = $this->_encodeFieldValue($field, $this->_getValue($field)); } } // collect metadata to set or ignore -- if we were unable to decode // certain metadata when reading it, we set it to false to indicate it // should be left alone (could be third-party data for example) foreach ($this->_metadata as $field => $data) { $field = "_" . $field; if (!empty($data)) { $attributes[$field] = $this->_encodeMetadata($data); } else if ($data === false) { $ignore[] = $field; } } // determine the fields to clear - as above, we clear any attributes // we don't know about so long as they aren't listed as ignored. foreach ($file->getAttributes() as $key => $value) { if (!array_key_exists($key, $attributes) && !in_array($key, $ignore)) { $clear[] = $key; } } $file->setAttributes($attributes); $file->clearAttributes($clear); // if we're not in a batch, submit file to perforce if (!$adapter->inBatch()) { if (!$description) { $description = $this->_generateSubmitDescription(); } // default option is to 'accept yours' but we switch to // null if SAVE_THROW_CONFLICTS flag is passed. $resolveFlag = P4_File::RESOLVE_ACCEPT_YOURS; if (in_array(static::SAVE_THROW_CONFLICT, (array)$options)) { $resolveFlag = null; } $file->submit($description, $resolveFlag); } return $this; }
P4Cms_Record::setAdapter | ( | P4Cms_Record_Adapter $ | adapter | ) |
Override parent to clear associated p4 file if adapter has changed to ensure the file will get the connection from the new adapter.
P4Cms_Record_Adapter | $adapter | the adapter to use for this instance. |
Reimplemented from P4Cms_Record_Connected.
{ // if adapter has changed, clear associated p4 file. if ($adapter !== $this->_adapter) { $this->_p4File = null; } return parent::setAdapter($adapter); }
P4Cms_Record::setFieldMetadata | ( | $ | field, |
array $ | data = null |
||
) |
Set metadata for the given field.
Field metadata is stored in a file attribute named for the field, but with a leading underscore (e.g. '_field-name').
string | $field | the field to set metadata for. |
array | null | $data | the metadata to store for the field. |
P4Cms_Record_Exception | if the field does not exist. |
{ if (!$this->hasField($field)) { throw new P4Cms_Record_Exception( "Cannot set field metadata for a non-existant field." ); } $this->_metadata[$field] = $data; return $this; }
P4Cms_Record::setId | ( | $ | id | ) |
Set the id of this record.
string | int | null | $id | the identifier of this record. |
Reimplemented from P4Cms_Model.
Reimplemented in P4Cms_Categorization_CategoryAbstract, P4Cms_Content_Type, P4Cms_Content, and Url_Model_Url.
{ if ($id !== null && !$this->isValidId($id)) { throw new InvalidArgumentException("Cannot set id. Given id is invalid."); } // if populate was deferred, caller expects it // to have been populated already. $this->_populate(); // if id has changed, clear associated p4 file. if ($id !== $this->getId()) { $this->_p4File = null; } return parent::setId($id); }
P4Cms_Record::setValue | ( | $ | field, |
$ | value | ||
) |
Set a particular field value.
Extends parent to validate names of new fields.
string | $field | the name of the field to set the value of. |
mixed | $value | the value to set in the field. |
P4Cms_Model_Exception | if the field does not exist. |
P4Cms_Record_Exception | if the field name is invalid. |
Reimplemented from P4Cms_Model.
{ // if field is new, validate field name. if (!$this->hasField($field)) { $validator = new P4Cms_Validate_RecordField; if (!$validator->isValid($field)) { throw new P4Cms_Record_Exception( "Cannot set value. Field '$field' is not a valid field name." ); } } return parent::setValue($field, $value); }
P4Cms_Record::setValues | ( | $ | values, |
$ | filter = false |
||
) |
Set all of the model's values at once.
Extends parent to support passing a form object.
Accepting a form object permits special handling of certain form elements via the P4Cms_Record_EnhancedElementInterface. This interface requires a populateRecord() method which allows the element to make decisions and modify other aspects of the record object.
Zend_Form | array | null | $values | form or array of values to set on record. |
bool | $filter | optional - if true, ignores values for unknown fields. |
Reimplemented from P4Cms_Model.
{ // let parent deal with non-form input. if (!$values instanceof Zend_Form) { return parent::setValues($values, $filter); } // set values from form input. $form = $values; $values = $form->getValues(); foreach ($values as $field => $value) { // skip read-only fields. if ($this->isReadOnlyField($field)) { continue; } // skip filtered fields. if ($filter && !$this->hasField($field)) { continue; } // handle record-aware elements. $element = $form->getElement($field); if ($element instanceof P4Cms_Record_EnhancedElementInterface) { $element->populateRecord($this); } else { $this->setValue($field, $value); } } return $this; }
static P4Cms_Record::store | ( | $ | values = array() , |
P4Cms_Record_Adapter $ | adapter = null |
||
) | [static] |
Store a record.
Equivalent to the instance method save(), but offered as a static method for convenience.
array | string | null | $values | optional - list of values for the new record if a string is given, it will be taken as the record identifier - if no id given, a new UUID will be assigned. |
P4Cms_Record_Adapter | $adapter | optional - storage adapter to use. |
{ // normalize values to an array. if (!is_array($values)) { $values = array(static::$_idField => $values); } $record = static::create($values, $adapter); $record->save(); return $record; }
P4Cms_Record::toP4File | ( | $ | reference = false | ) |
Provides access to a copy of the p4_file object which is underlying the current record instance.
By default it returns a cloned copy, pass true to get a reference.
bool | $reference | optional - pass true to get a reference to the file |
{ return $reference ? $this->_getP4File() : clone $this->_getP4File(); }
P4Cms_Record::$_encodeIds = false [static, protected] |
Optionally, bin2hex encode identifiers when converting to/from depot filespecs to permit non-standard characters.
Reimplemented in Url_Model_Url.
P4Cms_Record::$_fields = array() [static, protected] |
Specifies the array of fields that the current Record class wishes to use.
The implementing class MUST set this property.
Reimplemented from P4Cms_Model.
Reimplemented in Category_Model_Category, Cron_Model_Cron, Workflow_Model_Workflow, P4Cms_Categorization_CategoryAbstract, P4Cms_Content_Type, P4Cms_Content, P4Cms_Menu, P4Cms_Record_Config, P4Cms_Widget, and Comment_Model_Comment.
P4Cms_Record::$_fileContentField = null [static, protected] |
Specifies the name of the record field which will be persisted in the file used to store the records.
If desired, the implementing class needs to set this property to match an entry defined in the $_fields array. If left null, all fields will persist as file attributes.
Reimplemented in Workflow_Model_Workflow, P4Cms_Content_Type, P4Cms_Content, and P4Cms_Record_Config.
P4Cms_Record::$_hasValidFields = null [static, protected] |
P4Cms_Record::$_id = null [protected] |
Reimplemented from P4Cms_Model.
P4Cms_Record::$_idField = 'id' [static, protected] |
All records should have an id field.
Reimplemented from P4Cms_Model.
Reimplemented in Category_Model_Category, P4Cms_Widget, Comment_Model_Comment, and Url_Model_Url.
P4Cms_Record::$_metadata = array() [protected] |
P4Cms_Record::$_needsFilePopulate = false [protected] |
P4Cms_Record::$_needsPopulate = false [protected] |
P4Cms_Record::$_p4File = null [protected] |
P4Cms_Record::$_storageSubPath = null [static, protected] |
Specifies the sub-path to use for storage of records.
This is used in combination with the records path (provided by the storage adapter) to construct the full storage path. The implementing class MUST set this property.
Reimplemented in Category_Model_Category, Cron_Model_Cron, Workflow_Model_Workflow, P4Cms_Categorization_CategoryAbstract, P4Cms_Content_Type, P4Cms_Content, P4Cms_Menu, P4Cms_Widget, Comment_Model_Comment, and Url_Model_Url.
P4Cms_Record::$_whereCache = array() [static, protected] |
const P4Cms_Record::ENCODING_FORMAT_JSON = "json" |
const P4Cms_Record::ENCODING_METADATA_KEY = "_encoding" |
const P4Cms_Record::FROM_FILE_IMPORT = 'import' |
const P4Cms_Record::SAVE_THROW_CONFLICT = "throw" |