Download the PHP package energylab/gacela without Composer
On this page you can find all versions of the php package energylab/gacela. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download energylab/gacela
More information about energylab/gacela
Files in energylab/gacela
Package gacela
Short Description Responsive Data Mapper for PHP
License MIT
Informations about the package gacela
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.
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 | |
---|---|---|
1 | Bobby Mcintire | [email protected] |
2 | Frankfurt McGee | [email protected] |
'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:
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:
And would be accessed like so:
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:
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
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:
Salesforce
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.
With those two namespaces registered to the same directory, I could declare a new Mapper (User) like so:
Or alternatively like this:
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.
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()
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:
Now we can load existing Users, create a new Post, or delete a User or Post.
Right now you're probably thinking, "Wait! This looks almost EXACTLY like every other ORM I've ever used, where's the benefit in creating two files where I only created one before?"
So far all we've looked at is the most basic scenario - one database table with a mapper that presents simple, default find() and findAll() methods with a Model that doesn't have any custom business logic.
We'll explore custom Mapper functions first.
Fetching Data using Mappers
The name of the Model class associated to a Mapper class defaults to the same name as the Mapper class. This can be overriden:
To Fetch a Single Record:
To Fetch Multiple Records with Simple Criteria:
Fetching Records Using Complex Criteria
Customizing the Data returned for the Model
Sometimes, it is desirable to pass data to the model that is not strictly represented in the underlying table for a specific model.
Lets assume that we want to track all login attempts for each user. We could add the following database table:
However, we'd like to be able to know the number of times each user has attempted to login along with the number of times they have successfully logged in. So we're going to modify the find() and findAll() queries for the User mapper to always include the number of login attempts and successful logins.
Controlling Business Logic with Models
So far everything we've done with the Models is pretty standard:
Getters, Setters and other magic
But what about all of the fancy schmanzy business logic that you need to manage?
In the 'users' table we store the full name in a single field, but what if we wanted to be able to use the first name and last name separately? For example, so that when a user logs in, you could output 'Hello Bobby' to the user.
In the User model we could do the following:
Let's also assume that whenever the content for a Post model is set, that we want to translate certain words (lame, boring, stupid) to a different word (AMAZING):
Another common case with Models is verifying whether a value is empty or not. As such, just like with get() and set()
and _get
Validation
Right here seems like a good place to take a second and explain how validation works in Gacela.
By default Gacela uses the field meta data from the Mapper to perform validation. For example, if you have a field declared as an integer in the underlying resource, then only integer values would validate as true for the field in the Model.
Gacela currently supports the following data types natively:
- Binary
- Bool
- Date
- Decimal
- Enum
- Float
- Guid
- Int
- Set
- String
- Time
Validating to make sure that the input data matches the underlying data type is great and all, but what about validating data for complex business rules? For example, what if we only want to allow users with email addresses from the gacela.com domain?
The simplest solution in this case is to simply extend the validate() method in the model.