SkillAgentSearch skills...

Gacela

An open-source PHP Data Mapper ORM and Domain Framework built on PHP 5.3

Install / Use

/learn @energylab/Gacela
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Mapping Data Structures to PHP Objects

Most useful applications interact with data in some form. There are multiple solutions for storing data and for each of those solutions, sometimes multiple formats in which the data can be stored. When using object-oriented PHP, that same data is stored, modified and accessed in a class.

Let's assume that you were creating your own blogging system. We'll assume initially that you have need want to create posts and you want to allow multiple users to author articles.

Storing the data in a hierarchical format with XML is fairly straightforward. Each 'user' is represented by a node named 'user' with a child 'contents' node to contain the user's blog posts.

<xml version="1.0>
	<user id="1" first="Bobby" last="Mcintire" email="bobby@gacela.com" />
	<user id="2" first="Frankfurt" last="McGee" email="sweetcheeks@gacela.com">
		<contents>
			<content id="id" title="Beginners Guide to ORMs" published="2013-05-22 15:31:00">
                In order to start, you need to read the rest of this user's guide.
            </content>
		</contents>
	</user>
</xml>

With a relational database, we would create two tables, one to hold the basic information about each user, and a table to hold their posts.

'users' table

| id | name | email | |-----|----------------|--------------------------| | 1 | Bobby Mcintire | bobby@gacela.com | | 2 | Frankfurt McGee | sweetcheeks@gacela.com |

'contents' table

| id | userId | title | content | published | |----|--------|-------------------------|--------------------------------|---------------------| | 1 | 2 | Beginners Guide to ORMs | Read the rest of the guide | 2013-05-22 15:31:00 |

The same data in PHP would be stored in classes like so:

class User {

	protected $data = [
		'id' => 1,
		'name' => 'Bobby Mcintire',
		'email' => 'bobby@kacela.com'
	];

	protected $contents = [];

}

class User {

	protected $data = [
		'id' => 2,
		'name' => 'Frankfurt McGee',
		'email' => 'sweetcheeks@kacela.com',
		'phone' => '9876543214'
	];

	protected $contents = [
        [
            'id' => 1,
            'userId' => 2,
            'title' => 'Beginners Guide to ORMs',
            'content' => 'Read this guide all the way to the end'
        ]
	];

}

As you can see the way that data is stored can be vastly different from the way that we interact with data in our application code.

This is called the object-impedance mismatch. A common design pattern has arisen to hide the complexities of the differences between data in application code and data stores called Object-Relational Mapping.

This design pattern was developed specifically to deal with the complexities of mapping relational database records to objects in code, but many of the same principles apply when dealing with any form of raw data because there is almost always some mismatch.

Common Solutions

The most common approach to Object-Relational Mapping, or ORM for short, is the Active Record pattern.

With Active Record, one class instance represents one Record from the Data Source. With an Active Record instance, business logic and data access logic are contained in a single object. A basic Active Record class would look like so:

class Model_User extends ORM
{

}

And would be accessed like so:

$user = ORM::find('User', 1);

// echo's Bobby Mcintire to the screen
echo $user->name;

$user->email = 'new.user@gacela.com'

$user->save();

Gacela's Basic Philosophies

Working with a Data Mapper for the first time can be quite a bit more difficult than working with a more basic approach like Active Record, but Gacela offers large dividends if you tackle the complexity upfront. When developing Gacela, the following were just the top features we thought every ORM should have:

  • Automatically discover relationships between classes and rules about the data contained within classes.
  • Separate data store activities from business logic activities so that our classes have a single responsibility.
  • Defaults that are easy to set up and use, but can handle complex mapping between business objects and the underlying data store.

Installation and Configuration

How to Install

Gacela can be installed with Composer.

Define the following requirement in your composer.json file:

{
    "require": {
        "energylab/gacela": "dev-develop"
    }
}

Configuration

Data Source Setup

Gacela assumes that in any given application there will be multiple sources of data, even if there just multiple databases that need to be used.

Currently there are two supported types of Data Sources for Gacela: Database & Salesforce. We plan to add support for Xml, RESTful Web Services, and SOAP Web Services as well as to fully support the differences between MySQL, MSSQL, Postgres, and SQLlite.

Gacela provides a convenience method to create a DataSource object from configuration parameters. Once a DataSource object is created, it is easily registered with the Gacela instance so that it is available from anywhere.

Relational Database

$source = \Gacela\Gacela::createDataSource(
    [
        'name' => 'db',
        'type' => 'mysql' // Other types would be mssql, postgres, sqllite
        'host' => 'localhost',
        'user' => 'gacela',
        'password' => '',
        'schema' => 'gacela'
    ]
);

\Gacela\Gacela::instance()->registerDataSource($source);

The default for Gacela is to name the database tables in the plural form (users, contents). Though this can be easily overridden. We'll look at an example of how to override table name later.

For example:

CREATE TABLE users (
    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(150) NOT NULL,
    `email` VARCHAR(200) NOT NULL
) ENGINE=Innodb;

CREATE TABLE contents (
    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    userId INT UNSIGNED NOT NULL,
    title VARCHAR(255) NOT NULL,
    content LONGTEXT NOT NULL,
    published TIMESTAMP NULL,
    CONSTRAINT `fk_user_contents` FOREIGN KEY (userId) REFERENCES users(id) ON DELETE RESTRICT
) ENGINE=Innodb;

Salesforce

$source = \Gacela\Gacela::createDataSource(
    [
        'name' => 'salesforce',
        'type' => 'salesforce',
        'soapclient_path' => MODPATH.'sf/vendor/soapclient/',
			/**
			 * Specify the full path to your wsdl file for Salesforce
			 */
		'wsdl_path' => APPPATH.'config/sf.wsdl',
		'username' => 'salesforceuser@domain.com.sandbox',
		'password' => 'SecretPasswordWithSalesforceHash',
		/**
		 * Specifies which Salesforce objects are available for use in Gacela
		 */
         'objects' => []
    ]
);

Registering Namespaces

Gacela contains its own autoloader and registers it when the Gacela instance is constructed. Gacela also registers its own namespace for its use. You will want to register a custom namespace for your application even if you only plan on creating Mappers and Models for your project.

/*
 * Assuming that you are bootstrapping from the root of your project and that you want to put your 
 * custom application code in an app directory
 */
\Gacela\Gacela::instance()->registerNamespace('App', __DIR__.'/app/');

/*
 * A handy trick if you want to put your Mappers/Models or other custom extensions for Gacela in the global 
 * namespace
 */
\Gacela\Gacela::instance()->registerNamespace('', __DIR__.'/app/');

With those two namespaces registered to the same directory, I could declare a new Mapper (User) like so:

/*
 * __DIR__.'/app/Mapper/User.php'
 */

<?php

namespace Mapper;

class User extends \Gacela\Mapper\Mapper {}

Or alternatively like this:

/*
 * __DIR__.'/app/Mapper/User.php'
 */

<?php

namespace App\Mapper;

use \Gacela\Mapper\Mapper as M;

class User extends M {}

Even more exciting is that Gacela allows for cascading namespaces so you can easily override default Gacela classes without having to modify the methods and classes that depend on the modified class. So lets say that you wanted to create a custom Model class where you could some default functionality for all of your models to use.

/*
 * __DIR__.'/app/Model/Model.php'
 */
<?php

namespace Model;

class Model extends \Gacela\Model\Model {}

?> // This breaks a PSR standard but is shown here to clarify the end of the file

/*
 * __DIR__'/app/Model/User.php'
 */

 <?php

namespace Model;

class User extends Model {}

?>

Personally, I always extend the base Model and Mapper classes in projects if for no other reason than it simplifies my class declarations.

Using Caching

Gacela supports caching on two levels, the first is to cache the metadata that it uses to determine relationships, column data types, and such. The second is to cache requested data. In order to use either, caching must be enabled in the Gacela instance.

Gacela will use any caching library that supports get(), set(), and increment()

$cache = new \Cache;

\Gacela\Gacela::instance()->enableCache($cache);

Basic Usage

As we noted previously, there are two separate functions provided by any given ORM;

  • Data Access
  • Business Logic

Most ORM's mash these two responsibilities into a single class that will contain custom methods for dealing with business or application logic problems as well as custom methods for finding or saving data back to the database. Gacela takes a different approach in that it separates these two functions into two separate, distinct classes:

  • Mappers (Data Access)
  • Models (Business or Application Logic)

To get our basic application up and running, I will need the following files and class definitions:

/*
 * Again assume that we have created an 'app' directory and registered it into the global namespace with Gacela.
 * 
 * As I mentioned before, I prefer to always override the defaul
View on GitHub
GitHub Stars29
CategoryDevelopment
Updated8mo ago
Forks6

Languages

PHP

Security Score

82/100

Audited on Jul 18, 2025

No findings