77
© RightNow Technologies, Inc. Connect for PHP Mark Rhoads [email protected] Senior Developer, API Team, Public API

rightnow 6

Embed Size (px)

DESCRIPTION

rightnow material

Citation preview

Page 1: rightnow 6

© RightNow Technologies, Inc.

Connect for PHP

Mark [email protected] Developer, API Team, Public API

Page 2: rightnow 6

What You’ll Learn in this Session

Understand what Connect for PHP is for.Understand key concepts of Connect for PHP.Build simple Connect for PHP scripts/snippets.Apply what you learn in this session to later more advanced sessions that use Connect for PHP to accomplish more complex goals.Use this presentation and examples as a reference to help you successfully accomplish more complex projects using Connect for PHP.

Page 3: rightnow 6

What is Connect for PHP for?

Using server-side PHP on the RightNow system to inspect or affect the state of objects exposed by the Connect Common Object Model (CCOM) via Connect for PHP.

Developers who are:Familiar with using PHP in server-side environs.Familiar with object oriented concepts in PHP 5.3.2Building server-side integrations or Customer Portal pages.

Page 4: rightnow 6

Other Related 2011 RDC Sessions

Connect Common APIs & Common Object Model, Introduction, What’s New & Roadmap

Tuesday 12:15-1:45pmConnect Common Query: Introduction to ROQL & Analytics APIs

Tuesday 2:00-3:00pmCustom Objects & AppBuilder: Introduction, What's New & Roadmap

Tuesday 3:15-4:45pmAdvanced RightNow Object Query Language - Advanced Queries, Custom Objects Queries and Optimization

Wednesday 10:15-11:15am (follows this Connect for PHP session after the break in the same room)

Advanced CP Framework: Using Custom Objects in Customer PortalWednesday 2:30pm-3:30pm (in the Amphitheater)

Page 5: rightnow 6

DisclaimerStatements included in this presentation, other than statements or characterizations of historical fact, are forward-looking statements. These forward-looking statements are based on our current expectations, estimates, and projections about our industry, management’s beliefs, and certain assumptions made by us, all of which are subject to change. Forward-looking statements can often be identified by the words such as “anticipates,” “expects,” “intends,” “plans,” “predicts,” “believes,” “seeks,” “estimates,” “may,” “will,” “should,” “would,” “could,” “potential,” “continue,” “ongoing,” similar expressions, and variation or negatives of these words. These forward-looking statements are not guarantees of future results and are subject to risks, uncertainties, and assumptions that could cause our actual results to differ materially and adversely from those expressed in any forward-looking statement.

The risks and uncertainties referred to above include, but are not limited to, our assessment of current trends in the CRM market, possible changes to our approach to CRM and our core product strategies, changes to the functionality and timing of future product releases, customer acceptance of our existing and newer products, possible fluctuations in our operating results and our rate of growth, interruptions or delays in our hosting operations, breaches of our security measures, and our ability to expand, retain, and motivate our employees and manage our growth. Further information on potential factors that could affect our financial results are included in our annual and periodic filings with the Securities and Exchange Commission.

By sharing our product roadmap with you, we are not undertaking an obligation to develop the software with the features and functionality discussed herein.

The forward-looking statements in this presentation are made as of June 8, 2011 (today). We undertake no obligation to revise or update publicly any forward-looking statement for any reason.

Page 6: rightnow 6

Past:Version 1.0 of Connect for PHP released in November 2010 with support for these 11 build-in system objects:

Plus, any Custom Objects that are defined in the system.The February 2011 release added the Variable object to the version 1.0 interface.

Present:As of the May 2011 release, the version 1.1 interface ofConnect for PHP adds the AnalyticsReport object and the ability to run a given report and retrieve its results.

Future:More objectsEase of use enhancementsCustomAttributes

Roadmap

• Account• Answer• Contact

• Incident• Opportunity• Organization

• ServiceProduct• Task

• SalesProduct• ServiceCategory• ServiceDisposition

Page 7: rightnow 6

Key Concepts

Object-oriented binding of the RightNow Connect Common Object Model (CCOM) for PHPUses namespaces for versioningActive-record-like methods on “primary”/crud-able objects (those that derive from RNObject):

fetch(), first(), find(), save(), destroy()Properties that represent foreign keys to other tables/objects are represented as object references of the expected type.“Lazy” Loading

fetch(), first(), find(), etc, minimally instantiate objects with just the ID.Think of objects returned by fetch(), first(), find() as “typed ID’s”. That is, until something else about the object is referenced that induces a fetch from the database, the object is merely a typed container that contains an ID.

Page 8: rightnow 6

Key Concepts (cont’d)

Same ID of the same object type is the same object instance, e.g:Contact::first( “ID = 1” ) === Contact::fetch( 1 )Objects of the same type with the same id are “shared” throughout the entire process.

MetadataExtended metadata is available to support generic processing and client user interfaces.Type information, naming, requirednessConstraints and descriptions can be loaded into a client UI and enforced/presented there (e.g. via Javascript).See “Using Metadata”, below.

Page 9: rightnow 6

Key Concepts (cont’d)

Error handlingErrors are thrown as exceptions.Return values are never used to indicate error status.Empty/null results, e.g. a query that doesn’t match anything, will return null. However, a bad query will throw an exception.

Constraint tests are performed upon assignmentAnd sometimes upon save(). Some properties of some sub-objects (e.g. Email) have different constraints depending upon the hierarchy they are in, and so can only be tested if the hierarchy is known, or upon save().

Strong object typingAssigning the wrong type to a property will throw an exception.ID and LookupName are read-only on primary objects.ID and LookupName may only be “set” indirectly on primary objects by using fetch(), first(), find() or new/save().

Implicit commit & mail_commit upon successful (0) script exit.Non-zero exit codes will not commit.

Page 10: rightnow 6

ConnectPHP uses namespaces to version its interfaces and the presentation of the Connect Common Object Model.The version 1.1 interface to the classes, constants and static methods of CPHP is in the namespace:

RightNow\Connect\v1_1Version 1.2 would be in:

RightNow\Connect\v1_2The namespace, or an alias to a namespace must prefix the classname:$a_new_contact

= new RightNow\Connect\v1_1\Contact;

OR:use RightNow\Connect\v1_1 as RNCPHP;$a_new_contact = new RNCPHP\Contact;

Versioned Namespace

Page 11: rightnow 6

Versioned Namespace (cont’d)

PHP “use” statements must be in file or namespace scope.They cannot be specified within functions or any other block or scope.

The alias defined by the “use” statement must be unique within the file or namespace scope that it is declared.

Be careful when used in CP widgets as staging/deployment may put different widgets together in the same file.

Hereafter, “RNCPHP” will be used as a namespace alias as declared above.

Page 12: rightnow 6

Error Handling

Any operation upon or involving any CPHP object, method or property, including accessing or assigning a property may throw an exception.An exception thrown without a corresponding catch will result in a fatal uncaught exception error.Wrap code with try/catch!

Wrap blocks of related code.Wrap as large of a scope as you can reasonably handle/report errors to the user.Do let CPHP perform constraint testing for you (it will anyway).

• Easier to consume new versions should the constraints change.• Use CPHP metadata to communicate constraints to client code (e.g.

Javascript) so that constraints can be tested at the client without being hard-wired.

Page 13: rightnow 6

Error Handling (cont’d)

ConnectPHP Exceptions derive from the base PHP Exception class.RNCPHP\ConnectAPIError for run-of-the-mill errors

Use ->getMessage() to get error message.Use ->getCode() to get error code

• RNCPHP\ConnectAPIError::ErrorMisc• RNCPHP\ConnectAPIError::ErrorInvalidAccess• RNCPHP\ConnectAPIError::ErrorInvalidField• RNCPHP\ConnectAPIError::ErrorInvalidLogin• RNCPHP\ConnectAPIError::ErrorInvalidID• RNCPHP\ConnectAPIError::ErrorIncomplete• RNCPHP\ConnectAPIError::ErrorQueryTimeout• RNCPHP\ConnectAPIError::ErrorUnknown• RNCPHP\ConnectAPIError::ErrorInvalidRequest• RNCPHP\ConnectAPIError::ErrorConstraint• RNCPHP\ConnectAPIError::ErrorTypeMismatch

Page 14: rightnow 6

Error Handling (cont’d)

RNCPHP\ConnectAPIErrorFatal for fatal errors where the script must exit as quickly as possible.

Further use of ConnectPHP will result in ConnectAPIErrorFatal exceptions being thrown.

RNCPHP\ConnectAPIErrorBase or \Exception will catch either RNCPHP\ConnectAPIError or RNCPHP\ConnectAPIErrorFatal.

Test the severity property for RNCPHP\ConnectAPIError::SeverityAbortImmediatly

and/orUse the instanceof operator to distinguish between exceptions in the catch block.

Page 15: rightnow 6

Object Lifecycle

Page 16: rightnow 6

Fetching and Finding Objects

fetch( $ID [, $options ] ) is available as a static method for any primary object:$aContact = RNCPHP\Contact::fetch( 1,

RNCPHP\RNObject::VALIDATE_KEYS_OFF );

Specify VALIDATE_KEYS_OFF when the ID is likely to be a valid ID, or if the code is prepared to catch an “ErrorInvalidID” exception upon the first non-key property access. Using VALIDATE_KEYS_OFF can save one database hit and about 1ms in addition to avoiding one-time ROQL initialization.fetch() may specify a LookupName instead of an ID.

VALIDATE_KEYS_OFF is moot since the act of looking up the name validates the ID. Still slower since the name must be looked up.May throw an exception with ErrorInvalidID if the name does not uniquely match exactly one ID.

Page 17: rightnow 6

Fetching and Finding Objects (cont’d)

first( $query ) and find( $query ) are available as static methods for any primary object.

The $query parameter is the ROQL “WHERE” clause.first() will return the first object of the given type that matches the query, or NULL if none are found, e.g.:

RNCPHP\Contact::fetch( 1 )=== RNCPHP\Contact::first( 'ID = 1' );

find() will return the array of objects found to match the given $query:$carr = RNCPHP\Contact::find( 'ID = 1' );$carr[0] === RNCPHP\Contact::fetch( 1 );

Page 18: rightnow 6

Creating and Saving Objects

The save( [ $options ] ) method is available upon primary objects and is used to create or update the system state of a primary object.

$options may be:• RNCPHP\RNObject::SuppressExternalEvents• RNCPHP\RNObject::SuppressRules• RNCPHP\RNObject::SuppressAll

Nested references to primary objects are not traversed!Any newly created nested primary objects must be save()’d first to avoid an exception from being thrown.$a_inc = RNCPHP\Incident::fetch( 1 );$a_inc->PrimaryContact = new RNCPHP\Contact();…$a_inc->PrimaryContact->save();$a_inc->save();

Page 19: rightnow 6

Creating and Saving Objects (cont’d)

Use “new” to instantiate a new object:$a_new_org = new RNCPHP\Organization;

The ID property is read-only and is NULL upon a new instance until it is successfully save()’d:assert( is_null( $a_new_org->ID ) );

After filling any necessary required fields, save() the instance to create it in the system and to give it an ID:$a_new_org->Name = 'The New Name';$a_new_org->save();// just to demonstrate that the ID is there:assert( 0 < $a_new_org->ID );

Page 20: rightnow 6

Destroying Objects

destroy( [$options ] ) is a method on primary objects.Can’t get much easier:$org = RNCPHP\Organization::fetch( 1 );$org->destroy();

$options are the same as for save().Be prepared to catch Exceptions.

Page 21: rightnow 6

Rollback & Commit

The results of save() and destroy() operations are implicitly commit upon the successful completion (0 exit status) of the script.Or, a script may explicitly invoke rollback() and/or commit():

RNCPHP\ConnectAPI::rollback();RNCPHP\ConnectAPI::commit();

Once commit(), there’s no rolling-back;Be careful when catching exceptions

Catching an exception and continuing may induce a commit() if the script exits successfully.If you want to rollback upon an exception, gotta do it yourself or coerce a non-zero exit status of the script.

Page 22: rightnow 6

Getting Startedrequire_once(

get_cfg_var('doc_root').'/include/ConnectPHP/Connect_init.phph'

);initConnectAPI();

That’s it!More than once is okay

Putting it All Together

Page 23: rightnow 6

Putting it All TogetherCreating an object:// Create an object; an Organization in this example.$org = new RNCPHP\Organization;// Set the properties that we want to set$org->Name = 'The Parent Organization';$org->NumberOfEmployees = 5;

// Notice that the ID is not set// until the new object has been savedif ( isset( $org->ID ) )

throw new Exception( '$org->ID is unexpectedly set before saving for the first time' );

$org->save();echo( "Created Organization ID {$org->ID}<br>\n" );$the_org_id = $org->ID; // Save the ID for later

Page 24: rightnow 6

Putting it All TogetherUpdating an object:// Update a property on an// existing object fetched by ID:$org_by_id = RNCPHP\Organization::fetch( $the_org_id );$org_by_id->NumberOfEmployees = 10;$org_by_id->save();echo( "Updated Organization ID {$org_by_id->ID} to {$org->NumberOfEmployees} employees<br>\n" );

Page 25: rightnow 6

Putting it All TogetherUpdating an object (cont’d):// Update a property on an// existing object fetched by name:$org_by_name = RNCPHP\Organization::fetch( 'The Parent Organization' );

$org_by_name->NumberOfEmployees = 100;$org_by_name->save();echo( "Updated Organization ID {$org_by_name->ID} to {$org->NumberOfEmployees} employees<br>\n" );

// Notice that object references to the same// object refer to the same instance:assert( $org === $org_by_name ) || die( -1 );assert( $org === $org_by_id ) || die( -1 );

Page 26: rightnow 6

Putting it All TogetherDestroying an object:// Destroy the object:$org->destroy();echo( "Destroyed Organization ID $the_org_id<br>\n" );

// Notice that the ID is no longer set after the object has been destroyed

if ( isset( $org->ID ) )throw new Exception( '$org->ID is unexpectedly set

after destroying it' );

Page 27: rightnow 6

Putting it All Together

<html><head></head><body style='font-family:Comic Sans MS;font-size:14pt;'><?

// Setuprequire_once( get_cfg_var('doc_root'). '/include/ConnectPHP/Connect_init.phph' );

use RightNow\Connect\v1 as RNCPHP;

initConnectAPI();

// Create an object; an Organization in this example.$org = new RNCPHP\Organization;$org->Name = 'The Parent Organization';$org->NumberOfEmployees = 5;

// Notice that the ID is not set until the new object has been savedif ( isset( $org->ID ) )

throw new Exception( '$org->ID is unexpectedly set before saving for the first time' );

$org->save();echo( "Created Organization ID {$org->ID}<br>\n" );$the_org_id = $org->ID; // Save the ID for later

// Update a property on an// existing object fetched by ID:$org_by_id = RNCPHP\Organization::fetch( $the_org_id );$org_by_id->NumberOfEmployees = 10;$org_by_id->save();echo( "Updated Organization ID {$org_by_id->ID} to {$org->NumberOfEmployees}

employees<br>\n" );

// Update a property on an existing object fetched by name:$org_by_name = RNCPHP\Organization::fetch( 'The Parent Organization' );$org_by_name->NumberOfEmployees = 100;$org_by_name->save();echo( "Updated Organization ID {$org_by_name->ID} to {$org->NumberOfEmployees}

employees<br>\n" );

// Notice that object references to the same// object refer to the same instance:assert( $org === $org_by_name ) || die( -1 );assert( $org === $org_by_id ) || die( -1 );assert( 100 === $org->NumberOfEmployees ) || die( -1 );assert( 100 === $org_by_name->NumberOfEmployees ) || die( -1 );assert( 100 === $org_by_id->NumberOfEmployees ) || die( -1 );

// Destroy the object:$org->destroy();echo( "Destroyed Organization ID $the_org_id<br>\n" );

// Notice that the ID is no longer set after the object has been destroyedif ( isset( $org->ID ) )

throw new Exception( '$org->ID is unexpectedly set after destroying it' );

try{

// Trying to fetch the object again will fail$org_again = RNCPHP\Organization::fetch( $the_org_id );assert( !"If we get here, there's a bug!!!" ) || die( -1 );

} catch ( RNCPHP\ConnectAPIError $err ){

// Ignore the InvalidID error since we expected it// as part of this test.assert( RNCPHP\ConnectAPIError::ErrorInvalidID === $err->getCode() ) || die( -1 );

}

// Since this is just an example, rollback all changesRNCPHP\ConnectAPI::rollback();

echo( "Success!<br>\n" );

?><br><br><br></body></html>

A trivial example script as a CP page.Copy/paste into your favorite editor and upload to …cfg/scripts/euf/application/development/source/views/pages/ConnectPHPExample.php:

Page 28: rightnow 6

Putting it All Together Q&A

Page 29: rightnow 6

Custom Objects

ConnectPHP presents Custom Objects as the PackageName\ClassName under the versioned namespace.E.g. the RMA class in the CO package:$a_new_rma = new RightNow\Connect\v1_1\CO\RMA;

OR:use RightNow\Connect\v1_1 as RNCPHP;$a_new_rma = new RNCPHP\CO\RMA;

The interface to Custom Objects is versionedThe definition of a Custom Object is not versioned.

Otherwise, using a Custom Object in CPHP is the same as using any other CPHP object.Custom Object menus are presented as NamedID’s.Fields with supported relationships to built-in tables that map to CCOM objects or Custom Objects are presented as object references.

Page 30: rightnow 6

Custom Objects Q&A

Page 31: rightnow 6

Using Sub-Objects

Sub-objects in ConnectPHP are essentially nested structures contained within another object.

They are not crud-able. I.e., they do not have fetch(), first(), find(), save() or destroy() methods on them.

Access them just as any other property:$contact = RNCPHP\Contact::fetch( 1 );if ( is_null( $contact->Name ) ){

// Be sure to instantiate the sub-object// if it is not already there$contact->Name = new RNCPHP\PersonName;

}$contact->Name->First = 'First';$contact->Name->Last = 'Last';

Page 32: rightnow 6

Using Sub-Objects (cont’d)

Don’t want to bother with looking up the property type name of nested objects?

Was that a PersonName or a PersonFullName?Use ConnectPHP metadata.It’s also forward-compatible should the type name change:

$md = RNCPHP\Contact::getMetadata();$contact->Name = new $md->Name->type_name;

Page 33: rightnow 6

Sub-Objects Q&A

Page 34: rightnow 6

Using Custom Fields

Custom fields are represented as properties on the nested CustomFields property upon the object that has Custom Fields. E.g.:$inc = new RNCHP\Incident;$md = $inc::getMetadata();$inc->CustomFields = new $md->CustomFields->type_name;$inc->CustomFields->afield = "the field";

You might be familiar with the c$name specification used by legacy API’s such as the XML API. In ConnectPHP, the c$afield custom field on an incident is represented as the afield property on the CustomFields property/object on the Incident object.Using the CustomFields property/object in ConnectPHP is the same as with any other sub-object in ConnectPHP. See “Using Sub-Objects”, above.

Page 35: rightnow 6

CustomFields Q&A

Page 36: rightnow 6

Using Arrays

Lists in the Connect Common Object Model are presented by ConnectPHP as ConnectArray objects implementing the PHP ArrayAccess interface.Such properties are marked in the metadata of the property with a “true” value on the is_list property of the metadata:$md = RNCPHP\Organization::getMetadata();assert( true === $md->Addresses->is_list );

Access the elements of these properties just as you would an array:$org= RNCPHP\Organization::fetch( 1 );$org->Addresses[0]->AddressType->ID;

count() works:count( $org->Addresses );

“foreach” works but use “for” instead, especially if you’re not going to iterate over the entire array.Order is not guaranteed.

Page 37: rightnow 6

Using Arrays (cont’d)

Nillable lists are nullableAssigning a list to NULL will empty it:

$org->Addresses = NULL;

Cannot insert elements – can only append:$org->Addresses[] = new RNCPHP\TypedAddress;

Modifying an element property will cause it to be updated upon the next save() of the root primary object.Remove elements using the offsetUnset() method:$org->Addresses->offsetUnset( 0 );

Use “new” to create a new list or to replace an old one:$org->Addresses = new RNCPHP\TypedAddressArray;

Or:$md = RNCPHP\Organization::getMetadata();$org->Addresses = new $md->Addresses->type_name;

Page 38: rightnow 6

Arrays Q&A

Page 39: rightnow 6

Using File AttachmentsProperties that contain FileAttachments vary a bit in flavor, but FileAttachment items all derive from the FileAttachment class.$con = RNCPHP\Contact::fetch( 1 );$con->FileAttachments

= new RNCPHP\FileAttachmentCommonArray;$fa = new RNCPHP\FileAttachmentCommon;

There are two ways to begin with adding a file attachment:Via the setFile() method:// Assumes that $tmpfname is an existing file// in the “tmp” folder for the site:$fa->setFile( $tmpfname );

Via the makeFile() method:// Gets a file resource for the script to write to:$fp = $fa->makeFile();fwrite( $fp, __FUNCTION__." writing to tempfile\n" );fclose( $fp );

Page 40: rightnow 6

Using File Attachments (cont’d)

From there, wrap it up with:// Set the content type$fa->ContentType = 'text/plain';// Give it a name$fa->FileName = 'SomeName.suffix';// Append to the list$con->FileAttachments[] = $fa;$con->save();

Page 41: rightnow 6

Using File Attachments (cont’d)

File data is not directly exposed via ConnectPHP.However, a URL to the file is exposed on the URL property of a FileAttachment:$con->FileAttachments[0]->URL;

To present administrative access to a URL, the FileAttachment object has a getAdminURL() method:$con->FileAttachments[0]->getAdminURL();

Page 42: rightnow 6

File Attachments Q&A

Page 43: rightnow 6

Using NamedIDs

NamedID’s are a way of mapping between names and IDs upon a given property, such as when a property represents a “menu” selection or a named object.Like RNObjects, the first two properties are ID and LookupName.Unlike RNObjects, NamedID’s are not crud-able and have no fetch(), first(),find(), save() or destroy() methods.Code may set either the ID or the LookupName of a NamedID.

But not both!Once one is set, the other becomes read-only.

Many properties that are NamedID’s in ConnectPHP are candidates to eventually become primary object references. E.g. Country.Accessing the ID or LookupName of a NamedID or primary object is the same.

Only setting them is different.fetch(), first(), find() or save() for primary objectsvs direct assignment for NamedID’s.

Page 44: rightnow 6

Using NamedIDs (cont’d)

The set of possible NamedID pairs available upon a given property can be discovered from the metadata:

$md = RNCPHP\Account::getMetadata();$md->Country->named_values; // an array of NamedID’s

Or:RNCPHP\ConnectAPI::getNamedValues(

'RightNow\Connect\v1_1\Account', 'Country' );Or:RNCPHP\ConnectAPI::getNamedValues(

"RightNow\\Connect\\v1_1\\Account.Country" );

Notice the difference in how \ is treated in single-quoted vs double-quoted strings.

Page 45: rightnow 6

Using NamedIDs (cont’d)

The values for a NamedID are tied to it’s context.The LookupName and ID properties get their context from the container hierarchy. Otherwise, it’s just a NamedID and there is no context to allow mapping between the ID and LookupName.E.g. The Country property of an Account object is a NamedID.

This works:$md = RNCPHP\Account::getMetadata();$nid = new $md->Country->type_name;$nid->LookupName = 'US';$acct = $cphp_Account::fetch( 1 );$acct->Country = $nid;assert( 1 === $acct->Country->ID );

This doesn’t:$md = RNCPHP\Account::getMetadata();$nid = new $md->Country->type_name;$nid->LookupName = 'US';assert( 1 === $nid->ID ); // Will throw an Exception

Page 46: rightnow 6

NamedID Flavors

There are several flavors of NamedID’s.From a CPHP scripts’ point of view, the distinction is in the type name only, not in functionality.However, scripts must currently take care to assign the proper NamedID type when assigning a new NamedID to a property.This is most easily accomplished by using the metadata to discover the type:

$acct = RNCPHP\Account::fetch( 1 );$md = RNCPHP\Account::getMetadata();$acct->Country = new $md->Country->type_name;$acct->Country->LookupName = 'US';assert( 1 === $acct->Country->ID );

Page 47: rightnow 6

NamedID Q&A

Page 48: rightnow 6

NamedID Hierarchies

Much like NameIDs, but have a Parents array, and used to represent “hierarchical menus”.The “leaf” is the ID and LookupName immediately on the NamedIDHierarchy object.The “root”, or top of the hierarchy, is Parents[0].$opp = $cphp_Opportunity::first( 'Territory IS NOT NULL' );$idPath = array(); $namePath = array();$max = count( $opp->Territory->Parents );for ( $ii = 0; $ii < $max; $ii++ ) {

$idPath[] = $opp->Territory->Parents[$ii]->ID;$namePath[] = $opp->Territory->Parents[$ii]->LookupName;

}$idPath[] = $opp->Territory->ID;$namePath[] = $opp->Territory->LookupName;// e.g. /1/2/3echo( "idPath= /".join( '/', $idPath )."\n" );// e.g. /(a)/(b)/(c)echo( "namePath= /(".join( ')/(', $namePath ).")\n" );

Page 49: rightnow 6

NamedID Hierarchies (cont’d)

In version 1 of ConnectPHP, only found on SalesProduct.Folder, Opportunity.Territory, and the Source property on various objects. Like some NamedID properties, many NamedIDHierarchy properties are candidates to become primary objects in future versions.Compared to CWS, most NamedIDHierarchies in CWS are object references in ConnectPHP. In these cases there is also a read-only array on the object to represent the hierarchy, e.g.:

$org = RNCPHP\Organization::first('Parent IS NOT NULL' );

$idx = count( $org->OrganizationHierarchy ) - 1;assert( $org->Parent->ID

=== $org->OrganizationHierarchy[ $idx ]->ID );

Page 50: rightnow 6

NamedID Hierarchies Q&A

Page 51: rightnow 6

Using ROQL Object Queries

Use ROQL directly if fetch(), first(), and find() cannot do what you need.ROQL::queryObject( $queries ) returns a ROQLResultSet of the objects found to match the given queries, or NULL if nothing matched.The next() method upon the ROQLResultSet returns the ROQLResultof the next query, or NULL if there are no remaining results.The next() method upon the ROQLResult returns the primary object, or NULL if there are no remaining objects in the result.$rrs = RNCPHP\ROQL::queryObject(

‘SELECT contact FROM contact WHERE id = 1' );$rs = $rrs->next();$obj = $rs->next();assert( RNCPHP\Contact::fetch( 1 ) === $obj );

Page 52: rightnow 6

Use ROQL directly if fetch(), first(), and find() cannot do what you need.ROQL::query( $queries ) returns a ROQLResultSet of the rows found to match the given queries, or NULL if nothing matched.Similar machinations as with ROQL::queryObject(), but returns rows of tabular data instead of primary objects:$rrs = RNCPHP\ROQL::query(

‘SELECT id FROM contact WHERE id = 1' );$rs = $rrs->next();$row = $rs->next();// Tabular results are strings.// Though column/property names// are case-insensitive in ROQL,// they are not so in PHP:assert( '1' === $row['ID'] );

Using ROQL Tabular Queries

Page 53: rightnow 6

ROQL Q&A

Page 54: rightnow 6

Using AnalyticsReport

Available with the May 2011 releaseRead-only

Attempts to ->save() or ->destroy() an AnalyticsReport object will result in an exception being thrown.

Fetch an existing report definition by ID, LookupName, or by a ROQL query

Nothing special here, just like any other primary object.That is, use what you know about how to fetch other objectsBut … ROQL tabular queries can only fetch limited info about an AnalyticsReport, e.g. the ID & LookupName.ROQL object queries return the AnalyticsReport object.

To run a report, use the ->run() method on the AnalyticsReport object. The method signature looks like:public RNCPHP\AnalyticsReportResult run(

int $start = 0,RNCPHP\AnalyticsReportSearchFilterArray $filters

= NULL,int $limit = 10000 );

Page 55: rightnow 6

Use an AnalyticsReportResult just as you would a ROQLResult from a ROQL tabular query

The ->count() method returns the number of rows in the result.The ->next() method returns a row of results as an associative array indexed by the column headings:$ar = RNCPHP\AnalyticsReport::fetch( … );$arr = $ar->run();for ( $ii = $arr->count(); $ii--; ){

$row = $arr->next();echo( "Columns: "

.join( ',', array_keys( $row ) ) . "\n" );echo( "Values: "

.join( ',', $row ) . "\n" );}

Using AnalyticsReport (cont’d)

Page 56: rightnow 6

Using AnalyticsReport (cont’d)

Errors are communicated via Exceptions, so use try/catch just as you would for other ConnectPHP objects, properties and method

Some possible Exceptions:• Invalid Analytics Report: ID is not set• Invalid start: X is not >= 0• Invalid limit: X is not in the range 0-10000• Invalid Filter Name: ‘X’ at offset N is not a filter …• Filter not Editable: ‘X’ at offset N is not an editable filter …• Filter errors on AnalyticsReport …• There are no rows in this result

Page 57: rightnow 6

Supplying Modified FiltersNot all filters are editable

• Inspect AnalyticsReport.Filters.Attributes.EditableOnly need to specify the filters that are being overridden.Specify filters by name.An example:

$status_filter= new RNCPHP\AnalyticsReportSearchFilter;

$status_filter->Name = 'Status';$status_filter->Values = array( '2' );$filters = new RNCPHP\AnalyticsReportSearchFilterArray;$filters[] = $status_filter;$ar = RNCPHP\AnalyticsReport::fetch( $report_id );$arr = $ar->run( 0, $filters );

Using AnalyticsReport (cont’d)

Page 58: rightnow 6

Processing results:$ar = RNCPHP\AnalyticsReport::fetch( $report_id );$arr = $ar->run( 0, $filters );$nrows = $arr->count();if ( $nrows ) {

$row = $arr->next();// Emit the column headingsecho( join( ',', array_keys( $row ) ) ."\n" );// Emit the rows in this report runfor ( $ii = 0; $ii++ < $nrows; $row = $arr->next() ){

echo( join( ',', $row ) . "\n" );}

}

Using AnalyticsReport (cont’d)

Page 59: rightnow 6

AnalyticsReport Q&A

Page 60: rightnow 6

Using metadata

Metadata is available for every property in every CPHP objectDiscover the set of “primary” objects (those objects that can be fetched, saved and/or destroyed) with getPrimaryClassNames():

RNCPHP\ConnectAPI::getPrimaryClassNames() returns an array of strings representing the fully qualified PHP classnames, e.g. 'RightNow\Connect\v1_1\Contact'.

Access metadata using the getMetadata() static method on the corresponding class.:$md = RNCPHP\Account::getMetadata();

Property metadata is accessed by property name:print_r( $md->Country )

Page 61: rightnow 6

Using metadata (cont’d)

Interesting metadata on a property:type_name

• The fully qualified PHP type name, including namespace , of an object.COM_type

• The type of the property in the Connect Common Object Model.is_list

• True if the property is a list.is_nillable

• True if the property is nillable.is_object

• True if the property is an object.is_primary

• True if the property is a reference to a primary object.descriptiondefault

• The default value, if any. NULL if no default.

Page 62: rightnow 6

Using metadata (cont’d)

Interesting metadata on a property (cont’d):constraints

• An array of objects representing the constraints on the property.• Each array element is an object with the properties:

– kind, May be one of:» RNCPHP\Constraint::Min» RNCPHP\Constraint::Max» RNCPHP\Constraint::MinLength» RNCPHP\Constraint::MaxLength» RNCPHP\Constraint::MaxBytes

• If MaxBytes is present, MaxLength is only advisory.

» RNCPHP\Constraint::In» RNCPHP\Constraint::Not» RNCPHP\Constraint::Pattern

– value» The value of the constraint

Page 63: rightnow 6

Using metadata (cont’d)

Interesting metadata on a property (cont’d):Visibility:

• is_read_only_for_create– True if the property must not be set when save()-ing a new

object.• is_read_only_for_update

– True if the property must not be modified when save()-ing an existing object.

• is_required_for_create– True if the property must be set when save()-ing a new object.

• is_required_for_update– True if the property must be set when save()-ing an existing

object.• is_write_only

– The property is not read-able (e.g. NewPassword).

Page 64: rightnow 6

Metadata Q&A

Page 65: rightnow 6

Gotchyas

Misspelling property namesProperty names are case sensitive.No exceptions are thrown if assigning or accessing a misspelled property

No try/catch blockWill yield an ugly fatal error should an exception be thrown.

Calling exit(0), die( 0 ), or die( “…” ) will commit.Only save()’d changes are commit.

Merely changing a property does not cause it to be saved to the system.The save() method on the root primary object must be invoked.

Don’t use foreach to iterate over CPHP object properties.Use ConnectPHP metadata or get_object_vars() or get_class_vars() instead.

Object instances of the same type with the same ID are essentially global in scope. Modifying a property via one object reference is seen by all references to the same object, even if fetch()’d again.

Page 66: rightnow 6

Gotchyas (cont’d)

Not testing sub-objects for NULL before assigning a sub-field:$inc = RNCPHP\Incident::fetch( 1 );if ( is_null( $inc->CustomFields ) ){

// Be sure to instantiate the sub-object// if it is not already there$inc_md = $inc::getMetadata();$inc->CustomFields

= new $inc_md->CustomFields->type_name;}$inc->CustomFields->afield = "Some value";

print_r() doesn’t get everything.print_r() only sees as non-null those properties that have been previously accessed since the last save().print_r() does spew out everything on metadata.

Page 67: rightnow 6

Best Practices for ConnectPHP in CP

Keep “heavy lifting” in the model.Use a “use” statement to declare an alias to the version of ConnectPHP to target.Avoid using things that require that the versioned namespace be used or declared in a widget. But if you must, use a “use” statement to declare an alias that is unique among all widgets to avoid errors in the staged or deployed instance should the widgets happen to be on the same page/file.Avoid unnecessary copying of CPHP objects & properties.

Pass the CPHP object to the widget instead of copying properties.Avoid using CPHP objects as “scratch pads”.

CPHP primary object instances are global.“hitting” a CPHP property is slower than using a PHP variable.

Pay attention to Exception messages when debugging or when things go wrong … There really is info there to help you.

Page 68: rightnow 6

Best Practices for ConnectPHP in CP (cont’d)

Use VALIDATE_KEYS_OFF on the fetch() method.Avoids a “database hit”.Avoids one-time ROQL initialization.

Wrap blocks of code with try/catch.Prefer using ID’s when possible instead of LookupName to fetch() an object or to specify a NamedID.

Using LookupName instead of the ID may incur an extra db hit.Present “LookupName” to user/client UI, but map to ID at the client.

Using ROQL …Is backward compatible and works nicely with the Connect Common Object Model.Use ROQL when fetch(), first() and find() cannot do what you need, or when the result set is likely to be large.If you can, use fetch() with VALIDATE_KEYS_OFF instead to avoid ROQL one-time initialization.

Page 69: rightnow 6

Best Practices for ConnectPHP in CP (cont’d)

Test sub-objects for NULL before assigning a sub-field:$inc = RNCPHP\Incident::fetch( 1 );if ( is_null( $inc->CustomFields ) ){

// Be sure to instantiate the sub-object// if it is not already there$inc_md = $inc::getMetadata();$inc->CustomFields

= new $inc_md->CustomFields->type_name;}$inc->CustomFields->afield = "Some value";

Page 70: rightnow 6

Troubleshooting

Field values are not saved, even after ->save() and ::commit().Check that the field name has the correct spelling (Case Sensitive).Be certain that the script is correctly catching and handling exceptions.

• I.e., if your script ignores exceptions, then your script just might not be running to completion.

Check that the field of the given name exists in the metadata:$inc_md = RNCPHP\Incident::getMetadata();$inc_cf_md = $inc_md->CustomFields->type_name;$inc_cf_md = $inc_cf_md::getMetadata();assert( isset( $inc_cf_md->aFieldName ) );

Or if you prefer, testing for the field in get_class_vars() or using reflection to test for the name will work too.Check that the property is not write-only.

• If the property is write-only, then the contents will not be visible to the script.

Page 71: rightnow 6

Troubleshooting (cont’d)

Field values are not saved, even after ->save() and ::commit(). (cont’d)

Be certain that the containing sub-object has been properly assigned/instantiated:$inc = RNCPHP\Incident::fetch( 1 );if ( is_null( $inc->CustomFields ) ){

// Be sure to instantiate the sub-object// if it is not already there$inc_md = $inc::getMetadata();$inc->CustomFields

= new $inc_md->CustomFields->type_name;}$inc->CustomFields->aFieldName = 'Some value';

Page 72: rightnow 6

Troubleshooting (cont’d)

Field values are not saved, even after ->save() and ::commit(). (cont’d)

Is there a rule or external event that is overriding the value?Try passing the SuppressRules, SuppressExternalEvents or SuppressAll option to the save() method:$inc = RNCPHP\Incident::first( 'ID > 0' );$inc_md = $inc::getMetadata();$inc->Severity = new $inc_md->Severity->type_name;$inc->Severity->ID = 1;$inc->save( RNCPHP\RNObject::SuppressAll );

Use ROQL to validate that the value was saved (while debugging):$rrs = RNCPHP\ROQL\query(

"SELECT Severity FROM Incident"."WHERE ID = {$inc->ID}" );

$rs = $rrs->next();$row = $rs->next();assert( '1' === $row['Severity'] );

Page 73: rightnow 6

Troubleshooting (cont’d)

An exception is thrown when assigning or reading a propertyLog and read the exception message; There really is something useful there.try { … } catch ( RNCPHP\ConnectAPIError $err ) {

echo ( $err->getMessage() . "\n" );… }

When processing programmatically, use $err->getCode(), not getMessage().

• getCode() is backward compatible. getMessage() is not.Some common exceptions when assigning a property(This is not a complete list!)

• Type mis-match exceptions:– Type mismatch: Must be …

• Visibility/access exceptions:– Not Allowed: Cannot modify a read-only property– Not Allowed: Cannot be set to Nil/NULL

Page 74: rightnow 6

Troubleshooting (cont’d)

An exception is thrown when assigning or reading a property (cont’d)Some common exceptions when assigning a property (cont’d)

• Constraint exceptions– Min value exceeded: value X < Y minimum– Max value exceeded: value X > Y maximum– Min length not met: value X < Y minimum length– Max length exceeded: value X > Y maximum length– Max # bytes exceeded: value X > Y maximum # bytes– Pattern does not match: value ‘ABC' does not match pattern ‘^XYZ$’

• Named ID exceptions– Not allowed: NamedID.ID is already set

» Cannot change or set the LookupName of a NamedID– Neither ID nor LookupName are set– ID could not be resolved from the LookupName[X]– Unique ID could not be resolved from the LookupName[X]

Page 75: rightnow 6

Troubleshooting (cont’d)

An exception is thrown when when ->fetch()ing, ->save()ing or ->destroy()ing an object

Log and read the exception messagetry { … } catch ( RNCPHP\ConnectAPIError $err ) {

echo ( $err->getMessage() . "\n" );… }

When processing programmatically, use $err->getCode(), not getMessage().

• getCode() is backward compatible. getMessage() is not.Some common exceptions (not a complete list!):

• No such XYZ with ID = X– ID X does not exist for class XYZ

• No XYZ record found with LookupName = X• Nothing to save

– Cannot save an empty object• Class XYZ does not have {create,destroy,update} visibility• Required on {create,destroy,update}: X

– Property X is required

Page 76: rightnow 6

Other Resources

RightNow Developer Communityhttp://www.rightnow.com/developers.php

ConnectPHP 2011 Developer Conference slides:TBD

ROQL 2011 Developer Conference slides:TBD

Connect for PHP Documentation:http://community.rightnow.com/developer/fileexchange/Connect_PHP_November_2010/Default.html

Page 77: rightnow 6

Q&A