Pheasant Documentation

Pheasant is a bare-bones data mapper written specifically for PHP 5.3.1+ and MySQL. Rather than focus on abstraction and rich feature sets, Pheasant focuses on a performance and simplicity.

Despite it’s small foot-print (<5k LOC), Pheasant packs a punch and offers:

  • Easy persistence for objects: create, update and delete
  • Property mapping for common types
  • Relationships: One-to-one, One-to-many
  • Fluid querying
  • Events system for extension

Pheasant is Open Source and licensed under the BSD License.

Contents

Getting Started

This document will get you up and running with a basic Pheasant environment. You’ll need:

Installing

The easiest way to install Pheasant in your project is to use Composer and include the following in your composer.json file:

{
    "require": {
        "lox/pheasant": "1.1.*"
    }
}

After adding pheasant as a dependency of your project, update with composer update and you’ll have a working pheasant environment.

See Packagist for more details.

Loading and Connecting

Pheasant uses the class loading mechanism in Composer, which is basically a PSR-0 loader.

<?php

// use composer's autoload mechanism
require_once('vendor/autoload.php');

// setup database connections
Pheasant::setup('mysql://user:pass@localhost:3306/mydb');

Defining Objects

Anything mapped to the database is referred to as a Domain Object in Pheasant. The easiest way to define properties on a domain object is to simply extend PheasantDomainObject and implement a properties() method.

In the absence of an explicit definition, the tablename is inferred from the classname: post.

<?php

use \Pheasant\Types;

class Post extends \Pheasant\DomainObject
{
  public function properties()
  {
    return array(
      'postid'    => new Types\IntegerType(11, 'primary auto_increment'),
      'title'     => new Types\StringType(255, 'required'),
      'type'      => new Types\StringType(128, 'default=blog'),
      'timestamp' => new Types\DateTimeType(),
    );
  }
}

Properties are mapped to the same named column in the database. See mapping/types for more about what types are available and their parameters.

Creating Tables

Pheasant tends to stay out of the way when it comes to creating and altering tables, but it does provide a minimal helper for the creation of tables:

<?php

$migrator = new \Pheasant\Migrate\Migrator();
$migrator->create('post', Post::schema());

Alternately, DomainObjects provide a tableName() method that returns the table name as a string.

Saving and Updating

Whilst Pheasant uses the data mapper pattern, for convenience domain objects have activerecord-like helpers:

<?php

$post = new Post();
$post->title = "The joys of llama farming";
$post->timestamp = new \DateTime('2013-01-01');
$post->save();

Simple as that. Subsequent changes will update the record with whatever columns have been changed.

Finding

The core of Pheasant’s finder capability is based around find() and one(). Find returns a Collection, where one returns a single object.

See finding for more details.

<?php

// by identifier
$post = Post::byId(1);

// using a magic finder method
$posts = Post::findByTitleAndTimestamp('The joys of llama farming', '2013-01-01');
$posts = Post::findByType(array('blog', 'article'));

// by insertion order
$post = Post::find()->latest();

// paged, 1 - 100
$post = Post::find()->limit(1,100);

If you prefer direct sql, that works too and correctly handles binding null and array parameter. Note that ? is used for variable interpolation:

<?php

// using SQL interpolation
$post = Post::find('title LIKE ?', '%llama%');

// automatic IN binding
$posts = Post::find('type IN ?', array('blog', 'article'));

Relationships

An object defines what objects relate to it in the relationships() method.

See relationships for more details.

<?php

use \Pheasant;
use \Pheasant\Types;

class Post extends DomainObject
{
    public function properties()
    {
        return array(
            'postid'    => new Types\IntegerType(11, 'primary auto_increment'),
            'title'     => new Types\StringType(255, 'required'),
            'type'      => new Types\StringType(128, 'default=blog'),
            'timestamp' => new Types\DateTimeType(),
            'authorid'  => new Types\IntegerType(11)
        );
    }

    public function relationships()
    {
        return array(
            'Author' => Author::hasOne('authorid');
            );
    }
}

class Author extends DomainObject
{
    public function properties()
    {
        return array(
            'authorid' => new Types\IntegerType(11, 'primary auto_increment'),
            'fullname' => new Types\StringType(255, 'required')
            );
    }

    public function relationships()
    {
        return array(
            'Posts' => Post::hasOne('authorid')
            );
    }
}

// create some objects
$author = new Author(array('fullname'=>'Lachlan'));
$post = new Post(array('title'=>'My Post', 'author'=>$author));

// save objects
$author->save();
$post->save();

echo $post->title; // returns 'My Post'
echo $post->Author->fullname; // returns 'Lachlan'

Pheasant supports one-to-one, and one-to-many relationship types.

Configuration

Autoloading

Pheasant relies on a PSR-0 class-loader, such as the one in Composer.

<?php

// use composer's autoload mechanism
require_once('vendor/autoload.php');

MySQL Connection

The simplest way to configure Pheasant is to use static setup() method:

<?php

Pheasant::setup('mysql://user:pass@localhost:3306/mydb');

Note

It’s possible to pass in query parameters to this DSN for setting connection parameters:

<?php

Pheasant::setup('mysql://user:pass@localhost:3306/mydb?charset=utf8&strict=true');

Supported parameters are:

charset
The character set to set for the connection, defaults to utf8
timezone
Any mysql timezone string, defaults to UTC
strict
Set strict mode on the connection. This is generally not a good idea, as it creates issues with binding.
Multiple Connections

At present domain objects can only use the default connection, but you can setup and access other connections via the connection manager:

<?php

$pheasant = Pheasant::instance();

// define the connections
$pheasant->connections()->addConnection('mydb1', 'mysql://user:pass@localhost:3306/mydb');
$pheasant->connections()->addConnection('mydb2', 'mysql://user:pass@localhost:3306/another');

// look them up
$pheasant->connection('mydb1')->execute('SELECT * FROM mytable');

Allowing Pheasant objects to use different connections is a feature that is under development.

PSR-0
A standard for for interoperable PHP autoloaders See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md

Domain Objects

The domain object represents an object that can be persisted in Pheasant.

Initialization

A domain object has a Schema instance that defines everything about it including:

  • Properties
  • Relationships
  • Custom getter/setters
  • Events

The top-level Pheasant instance stores a mapping of class names to Schema instances. Whenever a domain object is instantiated or a static-method is called on one, the schema instance is checked. If one doesn’t exist, it’s initialized.

The initialization happens in DomainObject::initialize, for which the default implementation is to look for several template methods in the object.

properties()
A map of column names to Type objects (see mapping/types)
relationships()
A map of keys to RelationType objects representing 1-n or 1-1 relationships (see mapping/relationships)
tableName()
The database table name to map to, defaults to the name of the class
mapper()
The mapper instance to use, defaults to the mapping/rowmapper.

Property Access

Once properties have been defined in an objects schema, they are available via property access for read and write.

<?php

$post = new Post();
$post->title = 'Test Post';
$post->save();

echo $post->title; // shows 'Test Post'

$post->title = 'Updated Title';
$post->save();

Note

You can see which properties have been changed on a domain object in-between saves using the changes() method.

Calling save on an unchanged object won’t do anything.

Identity

A domain object has some sort of primary key. This is exposed within the domain object as an Identity. This object can be easily converted into a Criteria object for locating the object.

Any property that is either a Sequence or is defined with the primary option is considered part of the Identity. Composite keys are supported.

Constructors

The default constructor for a domain object allows for an array of key/values to be passed in:

<?php

$post = new Post(array('title'=>'Test Post'));
$post->save();

If you want to have a different constructor for your domain object, you must override the construct() method, as the actual __construct() method is final to ensure it’s always available.

<?php

class Post extends DomainObject
{
    public function construct($title)
    {
      $this->set('title', $title);
    }
}

$post = new Post('Test Post');
echo $post->title; // shows 'Test Post'

Inheritance

Inheritance and extending domain objects isn’t something that has any explicit support, although it would certainly be possible to override the properties method and extend it.