Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
API Design in PHP
          David Sklar
  Software Architect, Ning Inc.
      david@ninginc.com

    Zend Conference 2007
Ning Platform
Ning Platform
Ning

• PHP API provides interface to our platform
  REST APIs
• Live since August 2005 (with 5.0.4)
• Recent upgrade to 5.2.3
• In use in all 108,000+ networks on the
  platform
Ning

• Migration from XMLRPC to REST in 2005/6
• APIs used for content storage, user profile
  management, tagging, search, video
  transcoding, messaging, ...
• PHP (using APIs) runs in a hosted
  environment
API: XN_Content
<?php
$dinner = XN_Content::create('Meal');
$dinner->title = 'Salt Baked Combo';
$dinner->my->protein = 'seafood';
$dinner->my->ingredients = 
               array('shrimp','scallops','squid');
$dinner->save();
?>
PHP ➠ REST
POST /xn/atom/1.0/content
Content-Type: text/xml;charset=UTF-8

<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:xn="http://www.ning.com/atom/1.0"
       xmlns:my="http://afternoonsnack.ning.com/xn/atom/1.0">
 <xn:type>Meal</xn:type>
 <title type="text">Salt Baked Combo</title>
 <my:protein type='string'>seafood</my:protein>
 <my:ingredients type='string'>
   <xn:value>shrimp</xn:value><xn:value>scallops</xn:value>
   <xn:value>squid</xn:value>
 </my:ingredients>
</entry>
HTTP/1.1 200 OK
                      PHP ➠ REST
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:xn="http://www.ning.com/atom/1.0">
 <xn:size>1</xn:size>
 <updated>2007-08-28T22:11:47.420Z</updated>
 <entry xmlns:my="http://afternoonsnack.ning.com/xn/atom/1.0">
  <id>http://afternoonsnack.ning.com/502068:Meal:122</id>
  <xn:type>Meal</xn:type>
  <xn:id>502068:Meal:122</xn:id>
  <title type="text">Salt Baked Combo</title>
  <published>2007-08-28T22:11:47.414Z</published>
  <updated>2007-08-28T22:11:47.414Z</updated>
  <link rel="alternate"
        href="http://afternoonsnack.ning.com/xn/detail/502068:Meal:122" />
  <my:protein type="string">seafood</my:protein>
  ...
 </entry>
</feed>
Design Priorities

• Promote predictability, modularity, stability
• Choose human performance over computer
  performance
• Make efficiency easy, make inefficiency hard/
  impossible
At the start...

• Write code before you write the API
• Use cases, Use cases, Use cases
• Names matter (but don’t discuss them
  forever)
Use the API before it
            exists
Sketch out what you want to do....
Use Cases First!


• What does the API need to do?
• (Not “what could it do?”)
Need-driven
        Development

• Adding is easy. Removing is hard.
• You have lots of freedom with arguments
• Accessors provide insulation
Arguments
Long parameter lists are toxic:
<?php

function save($data, $flavor = null, $scope = null,
              $commit = null, $cascade = null,
              $permissions = null) {
    if (is_null($flavor))      { $flavor = 'quick'; }
    if (is_null($scope))       { $scope = 'global'; }
    if (is_null($commit))      { $commit = true; }
    if (is_null($cascade))     { $cascade = false; }
    if (is_null($permissions)) { $permissions = 0755; }
    // ...
}
Bread Stuffing
Bread Stuffing
What does this do?
<?php

save($data, null, null, true, false);

?>
How about this?
<?php

save($data, array('scope' => 'local'));

?>
Ahh, much better:
<?php

function save($data, $paramsOrFlavor = null,
               $scope = null, $commit = null,
              $cascade = null, $permissions = null){
    if (is_array($paramsOrFlavor)) {
        // ...
    }
    else {
        // ...
    }
}
Fun with __get() and __set()
public function __get($name) {
  switch ($name) {
    case self::screenName:
      return $this->_screenName;
    case self::fullName:
      return $this->_fullName;
    case self::uploadEmailAddress:
      $this->_lazyLoad('uploadEmailAddress');
      return $this->_uploadEmailAddress;
    case 'description':
      // ...
}
Static ‘n’ Dynamic Analysis



• find + grep
• tokenizer
• hooks + logging
find + grep
find . -name *.php -exec grep -H '::load('

   • easy
   • fast
   • mostly correct: watch out for dynamic
     variable names, text collision, etc.
tokenizer


• php-specific knowledge, but...
• can be slower
• need to write custom rules and parsing
$tokens = array(T_INCLUDE => 0, T_INCLUDE_ONCE => 0,
                T_REQUIRE => 0, T_REQUIRE_ONCE => 0);

foreach (new PhpFilterIterator(new RecursiveIteratorIterator(
         new RecursiveDirectoryIterator($root))) as $f) {
  $muncher = new Tokenmunch(file_get_contents($f));
  foreach ($muncher as $token) {
    if (array_key_exists($token[0], $tokens)) {
      $tokens[$token[0]]++;
      $startOfLine = $muncher->scanBackFor(T_WHITESPACE,"/n/");
      $startOfBlock = $muncher->scanBackFor(T_OPEN_TAG);
      $previousComment = $muncher->scanBackFor(T_COMMENT,"/n$/");
      $startPosition = max($startOfLine, $startOfBlock, $previousComment) +1;
      $endOfLine = $muncher->scanForwardFor(T_STRING, '/^;$/');
      $slice =  $muncher->sliceAsString($startPosition,
                                       $endOfLine - $startPosition+1);
      print trim($slice) . "n";
    }
  }
}
Zendcon 2007 Api Design
Hooks + Logging


• need to instrument the API beforehand
• watch out for performance overhead
API for the API: XN_Event
class XN_Event {
    /**
     * Fire an event with optional arguments
     *
     * @param string $event
     * @param array $args optional arguments to pass to listeners
     */
    public static function fire($event, $args = null);
                                          
    /**
     * Listen for an event
     *
     * @param string $event
     * @param callback $callback Function to run when the event is fired
     * @param array $args optional arguments to pass to the callback
     * @return string
     */
    public static function listen($event, $callback, $args = null);
}
XN_Event in Use
XN_Content::save() calls:
XN_Event::fire('xn/content/save/before', array($this));
// do the save
XN_Event::fire('xn/content/save/after', array($this));



This has been very useful for cache expiration.
Late Static Binding Workaround
Class name registry for static inheritance:

 W_Cache::putClass('app','XG_App');

 // ... time passes ...

 $className = W_Cache::getClass($role);
 $retval = call_user_func_array(
              array($className, $method),
              $args
           );
Names Matter


• Namespacing / collisions
• Versioning
Namespacing

• At Ning, “XN” means “hands off”
 • class names
 • property names
 • xml namespace prefixes
Versioning

• YourClass and YourClass2...sigh.
• Using include_path and auto_prepend_file
• Version number in REST URLs:
 http://app.ning.com/xn/atom/1.0/content/...
Docblocks:Yay!
/** It’s easy to generate human(-ish)
  * readable documentation from
  * docblocks. (@see PHPDocumentor,
  * @see doxygen)
  *
  * And the documentation is close
  * to the code.
  */
public function debigulator() {
}
Docblocks: Boo!
/** What about examples and tutorials
  * and all of the other thing that
  * are not method or class
  * specific?
  *
  * Is this documentation up to date
  * with the @version of the code?
  */
public function rebigulator() {
}
Too Much Sugar
// "System" Attribute
$content->title = 'Duck with Pea Shoots';

// "Developer" Attribute
$content->my->meat = true;
Non-literal Names..!
$attrs = array('title','my->meat');

foreach ($attrs as $attr) {
    print "$attr is " . $content->$attr;
}




            ☹
Alternatives

OK $content->title    and $content->my_flavor;

OK $content->xn_title and $content->flavor;

NO $content['title']  and $content->flavor;
Testing
      &
Code Coverage
The extent of your
        test suite
          is the
strength of your contract
     with your users.
Tools are secondary.

Discipline is primary.
Tools

• SimpleTest
 •   http://www.lastcraft.com/simple_test.php

• PHPUnit
 •   http://phpunit.de/




                                         ginger monkey - popofatticus@flickr - CC attrib 2.0
                                                         gorilla: tancread@flickr - CC by-nc
Future: Classes As “Types”
✓   $content->my->number = 123;

✓   $content->my->string = 'ronald';

✗   $content->my->date = '2005-03-10T12:24:14Z';

?   $content->my->set('date',
                     '2005-03-10T12:24:14Z',
                     XN_Attribute::DATE);
Future: Classes As “Types”
Newer:
✓   $content->my->date = 
          new DateTime('2005-03-10T12:24:14Z');

Older:
✓   $content->my->date = 
          XN_Date('2005-03-10T12:24:14Z');
class XN_Date {
    public $_stamp;
    
    public function __construct($timeOrDateString) {
        if (ctype_digit($timeOrDateString)) {
            $this->_stamp = $timeOrDateString;
        } else {
            $failure = (version_compare(phpversion(),'5.1.0') >= 0) ? -1 : false;
            $stamp = strtotime($timeOrDateString);
            if ($stamp === $failure) {
                throw new Exception("$timeOrDateString is not a valid time/date");
            } else {
                $this->_stamp = $stamp;
            }
        }
    }
    
    public function __get($property) {
        if ($property == 'epoch') {
            return (integer) $this->_stamp;
        } else if ($property == 'ISO8601') {
            return $this->__toString();
        } else {
            return gmdate($property, $this->_stamp);
        }
    }
    
    public function __toString() {
        return gmstrftime('%Y-%m-%dT%H:%M:%SZ',$this->_stamp);
    }
}
function XND_Date($timeOrDateString) { return new XND_Date($timeOrDateString); }
Resources
•   Joshua Bloch: "How to Design a Good API and
    Why It Matters"

    •   http://lcsd05.cs.tamu.edu/slides/keynote.pdf

•   Zend Framework Documentation

    •   http://framework.zend.com/manual/manual/

•   eZ Components Documentation

    •   http://ez.no/doc/components/overview/

•   These slides: http://www.sklar.com/blog/
Come work at                         !

• Write the code that powers 100,000+ social
  networks
• Write the next version of our PHP API
• Work in Palo Alto (or not)
• http://jobs.ning.com - david@ninginc.com

More Related Content

Zendcon 2007 Api Design