Location: PHPKode > scripts > gdapi-php > godaddy-gdapi-php-2855a9b/class/Client.php
<?php
/*
 * Copyright (c) 2012 Go Daddy Operating Company, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

namespace GDAPI;

class Client
{
  const VERSION = "1.0.8";

  const MIME_TYPE_JSON = 'application/json';

  /*
   * An instance of a class that implements CacheInterface.  If set,
   * it will be used to cache schemas so they are not requested repeatedly.
   *
   * @see setCache
   */
  static $cache = false;

  /*
   * Default options.  Do not edit this file to change these.
   * All the options can be overridden in the Client constructor.
   */
  static $defaults = array(
    /*
     * Responses are returned as abjects.  You can choose the class that is returned
     * for each response type.  Anything that isn't mapped will be returned as
     * an instance of the default_class (below).  Classes that you do map should
     * probably be a sub-class of Error, Collection, or Resource.
     */
    'classmap'            => array(
      'collection'        => '\GDAPI\Collection',
      'error'             => '\GDAPI\Error',
    ),

    /*
     * Behavior when errors occur:
     *  true:  An APIException (or subclass) will be thrown on errors.
     *  false: A response will be returned as an instance of the 'error' type defined in the classmap.
     */
    'throw_exceptions'    => true,

    /*
     * Optional prefix to add to cache keys
     */
    'cache_namespace'     => '',

    /* --------------------*
     * Less common options *
     * ------------------- */

    /*
     * Responses will be returned as instances of this class by default
     */
    'default_class'       => '\GDAPI\Resource',

    /*
     * HTTP client class to use.  Clients must implement the RequestInterface interface
     */
     'request_class'      => '\GDAPI\CurlRequest',

    /*
     * If set, the schema definition will be loaded from this file path instead of the base URL.
     */
    'schema_file'         => '',

    /*
     * If set, responses will be written directly to stdout/the browser
     */
    'stream_output'       => false,

    /*
     * Verify SSL certificate when connecting
     */
    'verify_ssl'          => true,

    /*
     * Path to a PEM file containing certificate authorities that are trusted
     */
    'ca_cert'             => '',

    /*
     * Path to a directory containing certificate authorities that are trusted
     */
    'ca_path'             => '',

    /*
     * Timeout for establishing an initial connection to the API, in seconds.
     *  cURL >= 7.16.2 support floating-point values with millisecond resolution.
     */
    'connect_timeout'     => 5,

    /*
     * Timeout for receiving a response, in seconds.
     *  cURL >= 7.16.2 support floating-point values with millisecond resolution.
     */
    'response_timeout'    => 300,

    /*
     * Time to keep HTTP connections open, in seconds.
     */
    'keep_alive'          => 120,

    /*
     * Enable GZIP compression of responses.
     */
    'compress'            => true,

    /*
     * Network interface name, IP address, or hotname to use for HTTP requests
     */
    'interface'           => '',

    /*
     * Name => Value mapping or HTTP headers to send with every request.
     */
    'headers'             => array(
      'Accept'  => self::MIME_TYPE_JSON,
    ),

    /* -------------------------*
     * Even less common options *
     * ------------------------ */

    /*
     * Follow HTTP redirect responses
     */
    'follow_redirects'    => true,

    /*
     * Limit to how many HTTP redirects will be followed before giving up.
     */
    'max_redirects'       => 10,

    /*
     * The attribute to look at to determine the class a response should be mapped to.
     */
    'type_attr'           => 'type',

    /*
     * The maximum depth of a JSON object when parsing
     */
    'json_depth_limit'    => 50,

    /*
     * Path to a PEM file containing a client certificate to use for requests.
     */
    'client_cert'         => '',

    /*
     * Path to a PEM file containing the key for the client certificate.
     * The cert and key may be in the same file, if desired.
     */
    'client_cert_key'     => '',

    /*
     * Password for the certificate key, if needed.
     * Can be provided as a string, or an anonymous function that returns a string.
     */
    'client_cert_pass'    => '',
  );

  /*
   * An array of active clients.
   * Only one instance per [base_url,access_key,secret_key] combination will be created.
   */
  static $clients = array();

  /*
   * A unique identifier for the [base_url,access_key,secret_key] combination
   */
  protected $id;

  /*
   * The base URL for the API.  Should include a version.
   */
  protected $base_url;

  /*
   * Options for this client
   */
  protected $options;

  /*
   * The schema loaded for this API
   */
  protected $schemas;

  /*
   * An instance of a class that implements RequestInterface, to make REST requests.
   */
  protected $requestor;

  /*
   * Create a new Client.
   *
   * @param string $base_url:   The root URL for the API you want to connect to.  This should include version.
   * @param mixed  $access_key: The access key for your API user, or an anonymous function that returns it.
   * @param mixed  $secret_key: The secret key for your API user, or an anonymous function that returns it.
   * @param array  $options:    A hash map of options to override the default options (see static::$defaults).
   *
   * @returns \GDAPI\Client
   */
  public function __construct($base_url, $access_key, $secret_key, $options=array())
  { $this->id = md5($base_url);
    $this->base_url = $base_url;
    $this->options = array_replace_recursive(static::$defaults, $options);

    $request_class = $this->options['request_class'];
    $this->requestor = new $request_class($this, $this->base_url);

    if ( $access_key && $secret_key )
    {
      $this->requestor->setAuth(static::resolvePassword($access_key), static::resolvePassword($secret_key));
    }

    $this->loadSchemas();
    
    static::$clients[$this->id] = $this;
  }
  
  /*
   * Destruct
   */
  public function __destruct()
  {
    unset(static::$clients[$this->id]);
  }

  /*
   * Set a single option
   * @param string $name Option name
   * @param string $value Option value
   */
  public function setOption($name,$value)
  {
    $this->options[$name] = $value;
  }

  /*
   * Set multiple options
   * @param array $opts name => value map of options
   */
  public function setOptions($opts)
  {
    foreach ( $opts as $k => $v )
    {
      $this->options[$name] = $value;
    }
  }

  public function getOptions()
  {
    return $this->options;
  }

  /*
   * Returns a password from a (possible) function that returns one
   *
   * @param string $str_or_fn A password string, or a function that returns one
   *
   * @returns string Password
   */
  static function resolvePassword($str_or_fn)
  {
    if ( is_string($str_or_fn) )
    {
      return $str_or_fn;
    }
    else
    {
      return $str_or_fn();
    }
  }

  /*
   * Get an instance of a client by id
   *
   * @param string $id: The client id
   * 
   * @return object Client
   */
  static function &get($id)
  {
    if ( isset(static::$clients[$id]) )
    {
      return static::$clients[$id];
    }
    
    return null;
  }

  /*
   * Set the caching implementation
   *
   * @param object $class The caching object to use
   */
  static function setCache($class)
  {
    static::$cache = $class;
  }

  /*
   * Load a schema and create Type objects
   *
   * @param string $path The path for the schema, relative to base_url
   */
  protected function loadSchemas($path='/')
  {
    $res = false;
    $ns = $this->options['cache_namespace'];
    $cache_key = 'restclient_'. ($ns ? $ns.'_' : '') . $this->id .'_'. $path;
    $cache = static::$cache;

    // Fake a schema response with a file, if given
    if ( $this->options['schema_file'] )
    {
      $res = new Resource($this->id, json_decode(file_get_contents($this->options['schema_file'])));
    }

    // Try the cache
    if ( $cache && !$res )
    {
      $res = $cache::get($cache_key);
    }

    // Make a request for it
    if ( !$res )
    {
      $res = $this->requestor->request('GET', $path);
    }

    if ( !$res )
    {
      return $this->error('Unable to load API schema', 'schema');
    }

    if ( $res->getType() == 'apiversion' && $res->getLink('schemas') )
    {
      // The response was the root of a version, with a link to the schemas
      return $this->loadSchemas($res->getLink('schemas'));
    }
    elseif ( $res instanceof Collection && $res[0] && $res[0]->getType() == 'apiversion' )
    {
      // The response was an API root with no version
      return $this->error('The base URL "'. $this->base_url.$path .'" does not specify an API version to use', 'schema');
    }
    else if ( $res instanceof Collection && $res[0] && $res[0]->getType() == 'schema' )
    {
      // The response was a list of schemas

      $this->base_url = $res->getLink('root');

      // Setup all the types
      $this->types = array();
      for ( $i = 0 ; $i < count($res) ; $i++ )
      {
        $schema = $res[$i];
        $this->types[ $schema->getId() ] = new Type($this->id, $schema);
      }

      if ( $cache )
      {
        $cache::set($cache_key, $res);
      }
    }
    else
    {
      // No idea what the response is
      return $this->error('The base URL "'. $this->base_url.$path .'" does not look like an API version','schema');
    }
  }

  /*
   * Magic method to get a type
   *
   * @param string $name The type name
   * 
   * @return object Type
   */
  public function __get($name)
  {
    if ( !isset($this->types, $this->types[$name]) )
    {
      return $this->error('There is no type for "'. $name . '" defined in the schema', 'type');
    }

    return $this->types[$name];
  }

  /*
   * Get all the types defined.
   *
   * @return array Types
   */
  public function getTypes()
  {
    return $this->types;
  }

  /*
   * Perform a request
   *
   * @see RequestInterface
   */
  public function request($method, $path, $qs=array(), $body=null, $content_type=false)
  {
    $requestor = $this->getRequestor();
    return $requestor->request($method,$path,$qs,$body,$content_type);
  }

  /*
   * Gets the requestor object
   *
   * @return object Requestor
   */
  public function getRequestor()
  {
    return $this->requestor;
  }

  /*
   * Convert responses into the appropriate classes
   *
   * @param mixed   $data   The response data to be converted
   * 
   * @return object Type
   */
  public function classify($data)
  {
    return $this->classifyRecursive($data,0,'auto');
  }

  /*
   * Convert responses into the appropriate classes
   *
   * @param mixed   $data   The response data to be converted
   * @param int     $depth  The recursion depth; do not set directly. 
   * @param string  $depth  What type to treat the data as; do not set directly.
   * 
   * @return object Type
   */
  protected function classifyRecursive($data, $depth=0, $as='auto')
  {
    if ( $as === 'auto' )
    {
      if ( is_object($data) )
      {
        $as = 'object';
      }
      elseif ( is_array($data) )
      {
        $as = 'array';
      }
      else
      {
        $as = 'scalar';
      }
    }

    if ( $as === 'object' )
    {
      $type = null;
      $type_attr = $this->options['type_attr'];
      if ( isset($data->{$type_attr}) )
      {
        $type = $data->{$type_attr};
        if ( isset( $this->options['classmap'][$type] ) )  
        {
          $type = $this->options['classmap'][$type];
        }
        else
        {
          $type = null;
        }
      }

      if ( $type === null )
      {
        $type = $this->options['default_class'];
      }

      // Classify links, data, etc 
      foreach ( $data as $k => &$v)
      {
        if ( is_array($v) )
        {
          $v = $this->classify($v, $depth+1, 'array');
        }
        elseif ( is_object($v) && isset($v->{$type_attr},$data->links) && array_key_exists($k,$data->links) )
        {
          $v = $this->classify($v, $depth+1, 'object');
        }
      }

      $out = new $type($this->id, $data);
    }
    elseif ( $as === 'array' )
    {
      $out = array();
      foreach ( $data as $k => &$v )
      {
        $out[$k] = $this->classifyRecursive($v,$depth+1);
      }
    }
    else
    {
      $out = $data;
    }

    return $out;
  }

  /*
   * Throw errors
   *
   * @param string $message  The error message
   * @param string $status   The error code or HTTP status
   * @param array  $body     The response or error details
   *
   * @throws APIException
   */
  public function error($message, $status, $body=array())
  {
    if ( $this->options['throw_exceptions'] )
    {
      $class = '\GDAPI\APIException';

      if ( isset(APIException::$status_map[$status]) )
      {
        $class = APIException::$status_map[$status];
      }

      if ( !is_int($status) )
      {
        $status = -1;
      }

      $err = new $class($message, $status, $body);
      throw $err;
    }
    else
    {
      return $body;
    }
  }

  /*
   * Get metadata about the last request & response
   * 
   * @returns array
   */
  public function getMeta()
  {
    return $this->requestor->getMeta();
  }
}

?>
Return current item: gdapi-php