Perforce Chronicle 2012.2/486814
API Documentation

P4Cms_Record Class Reference

Provides persistent storage of data models in Perforce. More...

Inheritance diagram for P4Cms_Record:
P4Cms_Record_Connected P4Cms_Model P4Cms_ModelInterface Comment_Model_Comment Cron_Model_Cron P4Cms_Categorization_CategoryAbstract P4Cms_Content_Type P4Cms_Record_Config P4Cms_Record_PubSubRecord Url_Model_Url Workflow_Model_Workflow

List of all members.

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()

Detailed Description

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.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

P4Cms_Record::_decodeFieldValue ( field,
value 
) [protected]

Decode field's value if it is encoded (checks field metadata).

Parameters:
string$fieldthe field we are decoding
string$valuethe encoded value.
Returns:
mixed the decoded value (could be string or array).
    {
        $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).

Parameters:
string$idthe id to decode.
Returns:
string the decoded id.
    {
        return pack("H*", $id);
    }
P4Cms_Record::_decodeMetadata ( data) [protected]

Decode metadata (presumably from storage).

Parameters:
string$dataThe data to be decoded.
Returns:
mixed The 'decoded' data.
    {
        return strlen($data)
            ? Zend_Json::decode($data)
            : null;
    }
P4Cms_Record::_deferPopulate ( ) [protected]

Schedule populate to run when data is requested (lazy-load).

Returns:
P4Cms_Record provides fluent interface.
    {
        $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.

Parameters:
string$fieldthe field to encode the value for.
mixed$valuethe value to encode.
Returns:
string the encoded value.
    {
        $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).

Parameters:
string$idthe id to encode.
Returns:
string the encoded id.

Reimplemented in Url_Model_Url.

    {
        return bin2hex($id);
    }
P4Cms_Record::_encodeMetadata ( data) [protected]

Encode metadata for storage (using JSON).

Parameters:
mixed$dataThe data to be encoded.
Returns:
string The 'encoded' data.
    {
        return Zend_Json::encode($data);
    }
P4Cms_Record::_generateSubmitDescription ( ) [protected]

Generate a save description for this record.

Returns:
string a default submit description.
    {
        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').

Parameters:
string$fieldthe field to get metadata for.
Returns:
array the metadata for the given field.
    {
        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.

Returns:
P4_File corresponding P4 File instance.
    {
        // 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.

Parameters:
string$fieldthe name of the field to get the value of.
Returns:
mixed the value of the field.
Exceptions:
P4Cms_Model_Exceptionif 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.

Parameters:
P4Cms_Record_Query | array | null$queryoptional - query options to augment result. *
Returns:
P4Cms_Record_Query the query input normalized to a query object.
Exceptions:
InvalidArgumentExceptionif 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.

Parameters:
bool$excludeFileoptional - skip populating file content
Returns:
P4Cms_Record provides fluent interface.
    {
        // 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.

Parameters:
P4_File | null$filethe corresponding P4_File object.
Returns:
P4Cms_Record provides fluent interface.
Exceptions:
Record_Exceptionif 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.

Parameters:
P4Cms_Record_Query | array | null$queryoptional - query options to augment result.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
integer The count of all matching records

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.

Parameters:
array$valuesassociative array of keyed field values to load into the model.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
    {
        return new static($values, $adapter);
    }
P4Cms_Record::delete ( description = null)

Delete this record.

Parameters:
string$descriptionoptional - a description of the change.
Returns:
P4Cms_Record provides fluent interface.

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.

Parameters:
string$depotFilea record depotFile.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
string|int the id portion of the depotFile file spec.

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.

Parameters:
string | int$idthe id of the record to fetch.
P4Cms_Record_Query | array | null$queryoptional - query options to augment result.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
bool true if the record exists, false otherwise.
    {
        $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.

Parameters:
string | int$idthe id of the record to fetch.
P4Cms_Record_Query | array | null$queryoptional - query options to augment result.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
P4Cms_Record the requested record.
Exceptions:
P4Cms_Record_NotFoundExceptionif 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.

Parameters:
P4Cms_Record_Query | array | null$queryoptional - query options to augment result.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
P4Cms_Model_Iterator all records of this type.

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.

Parameters:
P4_File$filea p4 file instance to convert into a record.
string | array | null$optionsoptions to influence the operation: FROM_FILE_IMPORT - only the file's values are used, the id is ignored
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
P4Cms_Record the record instance generated from the file.
    {
        // 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.

Parameters:
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
string the depot path used to store this class of records.
    {
        // 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').

Parameters:
string$fieldthe field to get metadata for.
Returns:
array the metadata for the given field.
Exceptions:
P4Cms_Record_Exceptionif 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.

Returns:
array a list of field names for this model.
Exceptions:
P4Cms_Record_Exceptionif 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.

Returns:
string the name of the file content field.
Exceptions:
P4Cms_Record_Exceptionif 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.

Returns:
string|null the value of the id field.

Reimplemented from P4Cms_Model.

    {
        $id = parent::getId();

        // cast non-null ids to strings.
        return $id === null ? null : (string) $id;
    }
static P4Cms_Record::getIdField ( ) [static]

Return name of the id field.

Returns:
string name of id field.
    {
        return static::$_idField;
    }
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).

Parameters:
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
string the path used to store this class of records.
    {
        // 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.

Returns:
bool true if the class has a file content field; false otherwise.
    {
        return isset(static::$_fileContentField);
    }
static P4Cms_Record::idToFilespec ( id,
P4Cms_Record_Adapter adapter = null 
) [static]

Given a record id, determine the corresponding filespec.

Parameters:
string$idthe record id to get the filespec for.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
string the filespec for a given record id.

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.

Returns:
boolean true if record is deleted or doesn't have an id, otherwise returns true.
    {
        return $this->getId() && $this->_getP4File()->isDeleted();
    }
P4Cms_Record::isValidId ( id)

Determine if id is valid identifier for this record.

Parameters:
string$idrecord identifier
Returns:
boolean true if valid, otherwise false
    {
        $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.

Parameters:
string$idthe id of the record to remove.
P4Cms_Record_Adapter$adapteroptional - storage adapter to use.
Returns:
P4Cms_Record provides fluent interface.
    {
        // 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.

Parameters:
string$descriptionoptional - a description of the change.
null | string | array$optionsoptional - 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.
Returns:
P4Cms_Record provides a fluent interface

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.

Parameters:
P4Cms_Record_Adapter$adapterthe adapter to use for this instance.
Returns:
P4Cms_Record provides fluent interface.

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').

Parameters:
string$fieldthe field to set metadata for.
array | null$datathe metadata to store for the field.
Returns:
P4Cms_Record provides fluent interface.
Exceptions:
P4Cms_Record_Exceptionif 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.

Parameters:
string | int | null$idthe identifier of this record.
Returns:
P4Cms_Record provides fluent interface.

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.

Parameters:
string$fieldthe name of the field to set the value of.
mixed$valuethe value to set in the field.
Returns:
P4Cms_Model provides a fluent interface
Exceptions:
P4Cms_Model_Exceptionif the field does not exist.
P4Cms_Record_Exceptionif 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.

Parameters:
Zend_Form | array | null$valuesform or array of values to set on record.
bool$filteroptional - if true, ignores values for unknown fields.
Returns:
P4Cms_Record provides a fluent interface

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.

Parameters:
array | string | null$valuesoptional - 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$adapteroptional - storage adapter to use.
Returns:
P4Cms_Record provides a fluent interface.
    {
        // 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.

Parameters:
bool$referenceoptional - pass true to get a reference to the file
Returns:
P4_File the file associated with this record instance.
    {
        return $reference
            ? $this->_getP4File()
            : clone $this->_getP4File();
    }

Member Data Documentation

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]

The documentation for this class was generated from the following file: