Perforce PHP Object and Record Models Geoff Nicol Software Developer Perforce PHP Object Model Overview

• Abstract perforce operations • Do a lot of the parsing and assembly to avoid string bashing elsewhere • Create a more PHP natural layer • Wrap multiple connection types • Provide a reliable interface • Creates a foundation for PHP Record Model MODELS MODELS MODELS MODELS SPEC OUTPUT SAMPLE

p4 changes -m1 ... change 318652 ... time 1305914360 ... user gnicol ... client gnicol ... status submitted ... changeType public ... path //depot/main/application/...... desc Sed ut perspiciatis und

p4 change -o 318652 ... Change 318652 ... Date 2011/05/20 10:59:20 ... Client gnicol ... User gnicol ... Status submitted ... Description Sed ut perspiciatis unde omnis iste natus error sit voluptatem.

Accusantium doloremque laudantium.

... Type public CONNECTION HANDLING

• Connection Abstract defines base capabilities • Concrete wrappers for CLI and Native Extension • Static factory method does type selection • Supports concept of setting ‘default’ connection

• Connected Abstract • Implements [get/set/has/clear]Connection • Provides static getDefaultConnection to allow per-model defaults CONNECTION SAMPLE

$connection = P4_Connection::factory( 'perforce:1666', 'user', 'client', null );

P4_Connection::setDefaultConnection($connection); SPEC SAMPLE

// Assume default connection setup as per previous example $job = new P4_Job(); $job->setDescription('test')->save();

// Or use a manual connection $job = new P4_Job( P4_Connection::factory('other:1666', 'joe') ); $job->setValue('Description', 'test2')->save();

// will contain a partially populated version $jobs = P4_Job::fetchAll( array(P4_Job::FETCH_BY_FILTER => 'test') );

foreach($jobs as $job) { echo $job->getId(); // Pulled from partial population echo ': ' . $job->getDescription(); // Will lazy load } P4_File

• Wraps numerous perforce commands (fstat, print, attribute, etc.)

• Allows users to limit files via a Query API we provide • Limit results based on path(s) • Specify sorting options • Paginate results: max rows, row offset

• Exposes our Filter API to further limit results • Programmatic interface to fstat -F • Supports literal and regex matches • Conditions can be AND’, OR’d together • Supports sub-filters • Casts to fstat -F via toString() FILE SAMPLE

$file = new P4_File; $file->setFilespec('//depot/small.txt') ->open() ->setLocalContents('A small file.') ->setAttribute('foo', 'small') ->submit('Add a small file.');

$file->edit() ->setLocalContents('An edited small file.') ->submit('Edit a small file');

echo P4_File::fetch('//depot/small.txt') ->getDepotContents();

----- Output ------Edit a small file SUGAR

• Pass strings or objects when passing parameters like user, group, etc. • Spec word-lists are returned as associative arrays • Mutators accept hashes or strings as input • A fluent interface is supported wherever possible • Iterator provide convenience methods such as Invoke, Filter, Search, Sort SUGAR EXAMPLES

// Pass in object or string, where appropriate $group = new P4_Group; $user = P4_User::fetch('Bob'); $group->setId('test') ->addUser($user) ->addUser('joe') ->save();

// Pass in string or hash, where appropriate $triggers = P4_Triggers::fetch(); $values = $triggers->getTriggers(); $values[] = 'test-trigger form-in //... /path/script.sh'; $values[] = array( 'name' => 'test2', 'type' => 'form-out', 'path' => '//...', 'command' => '/path/otherScript.sh' ); $triggers->setTriggers($values)->save(); Perforce CMS Record Model Overview

• Storing data in perforce can be hard, this layer should make it easy. • Provide a natural CRUD interface more in line with Active Record • Provide features such as , fluent interface, etc. • Has the option of extension • Supports several modes of operation • Static • Object • Extended Object SIMPLE STATIC API

• store() • fetch() • fetchAll() • exists() • count() • remove() USING THE STATIC API

$record = P4Cms_Record::store( array( 'firstName' => 'Don', 'lastName' => 'Draper’ ) );

print_r($record->getValues());

----- Output ------Array( [id] => 1 [firstName] => Don [lastName] => Draper ) STORAGE LOCATION

/ /

• Storage Path is provided by the Record Adapter • We support a default adapter concept • Storage Sub-Path is, optionally, provided by the Record Model • ID is based on Record ID OBJECT MODEL

• Can instantiate record class • Provides in-memory data objects • get/setId() • get/setValues() • save() • delete() USING THE OBJECT MODEL

$peg = new P4Cms_Record; $peg->setValue('firstName', 'Peggy') ->setValue('lastName', 'Olson') ->save();

print_r($peg->getValues());

----- Output ------Array( [id] => 2 [firstName] => Peggy [lastName] => Olson ) EXTENDING THE OBJECT MODEL

• Why extend? • Specify a storage sub-path • Define defaults, known fields, id-field, file-field • Define custom field accessors, mutators • Provide access to related records • e.g. $person->getFriends() OTHER FEATURES

• Lazy-load • fetch() defers populate until getValue()

• IDs & Auto-increment • IDs can be strings e.g. ‘path/to/foo.bar’ • If no ID is given, store() makes one

• Query and Filter extended from Object layer

• Batches • Put modified records in a pending change • Can be committed or reverted • Faster, atomic USING THE FILTER API

$names = array('Pete', 'Betty', 'Joan', 'Roger'); foreach ($names as $name) { Person::store(array('firstName' => $name)); }

$filter = new P4Cms_Record_Filter; $filter->add('firstName', array('Betty', 'Joan')); $ladies = Person::fetchAll( new P4Cms_Record_Query(array('filter' => $filter)) );

print_r($ladies->invoke('getValue', array('firstName'));

----- Output ------Array( [0] => Betty [1] => Joan ) USING BATCHES

$people = Person::fetchAll(); $adapter = P4Cms_Record::getDefaultAdapter();

$adapter->beginBatch('Bulk Update'); $people->invoke('setValue', array('fictional', true)); $people->invoke('save'); $adapter->commitBatch();

// use the P4 layer to verify batching occured $changes = P4_Change::fetchAll(array('maximum' => 1)); print count($changes->current()->getFiles());

----- Output ------6 HOW DOES IT WORK?

• Record ‘Adapter’ hold connection object, specifies storage path, manages batches • Record IDs map to filenames • Values map to attributes • Query API leans heavily on p4 fstat • Uses our Perforce PHP Object Model CONCLUSION

• Perforce PHP Object Model • Provides deep access to perforce functionality • Removes the need to bash strings and spec forms manually • Provides a more consistent and fully detailed view of objects

• Perforce PHP Record Model • Provides a more ActiveRecord style interface to Perforce • Can be used in conjunction with Object Model Questions ???