Download the PHP package emapper/emapper without Composer
On this page you can find all versions of the php package emapper/emapper. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download emapper/emapper
More information about emapper/emapper
Files in emapper/emapper
Package emapper
Short Description The Extensible Data Mapper library for PHP
License BSD-2-Clause
Informations about the package emapper
eMapper
The Extensible Data Mapper library for PHP
Author: Emmanuel Antico
Version: 4.0
Changelog
2015-01-13 - Version 4.0.1
- Deprecated: Method throwException in Driver class.
- Fix: Cache option not being used in AssociationManager.
- Fix: Generating dynamic statements for Matches, StartsWith, GreaterThan.
- Fix: Getting index type only when receivin a Field instance in Manager class.
- Extended documentation.
Dependencies
- PHP >= 5.4
- Omocha package
- eMacros package
- SimpleCache package
Installation
Installation through Composer
About
eMapper is a PHP library aimed to provide a simple, powerful and highly customizable data mapping tool. It comes with some interesting features like:
- Customized mapping: Results can be mapped to a desired type through mapping expressions.
- Indexation and Grouping: Lists can be indexed or grouped together by a column/attribute name.
- Custom types: Developers can design their own types and custom type handlers.
- Cache providers: Obtained data can be stored in cache using APC or Memcache.
- Dynamic SQL: Queries can contain Dynamic SQL clauses.
- Entity Managers: Managers provide a set of ORM features which are common in similar frameworks.
- Fluent queries: Fluent queries can generate SQL programatically using a fluent interface.
Introduction
Step 1: Pick an engine
eMapper supports SQLite, PostgreSQL and MySQL (or MariaDB if you prefer). Creating a connection requires creating an instance of the corresponding driver.
Step 2: Initialize mapper instance
Now that the connection driver is ready we create an instance of the Mapper class.
Step 3: Fetching data
eMapper is a type-oriented framework. Everything revolves around queries and mapping expressions. The following examples try to give you an idea of how the data mapping engine works.
Mapping 101
Simple types
Arrays
Objects
Note: One important thing to remember when mapping to a structure is that values contained in columns declared using the DATE or DATETIME types are converted to instances of DateTime.
Lists
Simple types
Arrays and Objects
Indexes and Groups
Indexes
Groups
Callbacks
Queries
Query arguments
Arrays/Objects as argument
Note: The syntax for array/object attributes work as long as you provide the array/object as the first argument.
Fluent Queries
Fluent queries provide a fluent interface for generating SQL programatically. We obtain a new FluentQuery instance by calling the newQuery method in the Mapper class.
Select
Insert
Update
Delete
Filter methods
Use these methods along with the Column class to build conditional expressions. Adding false as a last argument produces a negated condition.
Name | Method | Arguments | Example |
---|---|---|---|
Equals | eq | 1 | Column::name()->eq('emaphp') |
Contains (case sensitive) | contains | 1 | Column::description()->contains('PHP') |
Contains (case insensitive) | icontains | 1 | Column::description()->icontains('PHP') |
In | in | 1 | Column::id()->in([1, 2, 3]) |
GreaterThan | gt | 1 | Column::price()->gt(100) |
GreaterThanEqual | gte | 1 | Column::price()->gte(100) |
LessThan | lt | 1 | Column::price()->lt(100) |
LessThanEqual | lte | 1 | Column::price()->lte(100) |
StartsWith (case sensitive) | startswith | 1 | Column::name()->startswith('ema') |
StartsWith (case insensitive) | istartswith | 1 | Column::name()->istartswith('ema') |
EndsWith (case sensitive) | endsswith | 1 | Column::name()->endswith('php') |
EndsWith (case insensitive) | iendswith | 1 | Column::name()->iendswith('php') |
Matches (case sensitive) | matches | 1 | Column::title()->matches('^The') |
Matches (case insensitive) | imatches | 1 | Column::title()->imatches('^The') |
IsNull | isnull | None | Column::last_login()->isnull() |
Range | range | 2 | Column::hits()->range(5,10) |
Stored procedures
In order to call a stored procedure we create a StoredProcedure instance by calling the newProcedure method in the Mapper class. The procedure is then invoked using the call method.
Entity Managers
Entities and Annotations
Entity managers are objects that behave like DAOs (Data Access Object) for a specified class. The first step to create an entity manager is designing an entity class. The following example shows an entity class named Product. The Product class obtains its values from the pŕoducts table, indicated by the @Entity annotation. This class defines 5 attributes, and each one defines its type through the @Type annotation. If the attribute name differs from the column name we can specify a @Column annotation indicating the correct one. As a general rule, all entities must define a primary key attribute. The Product class sets its id attribute as primary key using the @Id annotation.
Managers are created through the newManager method in the Mapper class. This method expects the entity class full name. Managers are capable of getting results using filters without having to write SQL manually. Notice that conditional expressions are now created using the Attr class instead of Column.
Manager utilities
Storing objects
Deleting objects
Associations
Introduction
Associations provide an easy way to fetch data related to an entity. As expected, they also need to be declared using a special set of annotations. Their types include one-to-one, one-to-many, many-to-one and many-to-many.
One-To-One
The next example shows 2 entities (Profile and User) and how to declare a One-To-One association between them to obtain a User entity from a fetched profile.
In this example, the Profile class declares a user property which defines a one-to-one association with the User class. The @Attr annotation specifies which property is used to perform the required join. When an attribute is declared on the current class then it must be expressed between parentheses right after the annotation.
The User class defines the profile property as a one-to-one association with the Profile class. Notice that this time the @Attr annotation defines the property name without parentheses, meaning that the required attribute is not declared in the current class. Also, this association is declared as lazy, which means that is not evaluated right away. The following example shows how to obtain a Profile instance and its associated user.
Lazy associations returns an instance of eMapper\ORM\AssociationManager. This means that invoking the getProfile method will return a manager instance, not an entity. In order to get the referred value we append a call to the fetch method.
Associations also provide a mechanism for querying for related attributes. Suppose we want to obtain a profile by its user name. We can do this by using a special syntax that specifies the association property and the comparison attribute separated by a doble underscore.
One-To-Many and Many-To-One
Suppose we need to design a pet shop database to store data from a list of clients and their respective pets. The first step after creating the database will be implementing the Client and Pet entity classes. The Client class has a one-to-many association with the Pet class provided through the pets property. The required attribute (clientId) is specified as a value of the @Attr annotation. This annotation references the attribute in the Pet class that stores the client identifier.
From the point of view of the Pet class this is a many-to-one association. The owner association is resolved through the clientId attribute , meaning that in this case it has to be specified between parentheses.
This small example obtains all clients that have dogs.
And this one obtains all pets for a given client.
Many-To-Many
Many-To-Many associations are kind of special as they need to provide the name of the join table that resolves the association. Suppose that we want to add a favorites association from the User class to the Product class.
The @Join annotation must indicate which columns are used to perform the join along with the table name. Columns must be specified in the right order; first being the one that references the current entity. The following code shows an example using this association.
Recursive associations
There are some scenarios in which an entity is associated to itself in more than one way. In this example we'll introduce the Category entity class to explore in detail these type of associations.
The Category class its related to itself through the parent and subcategories associations. Both of them need to specify the parentId attribute as the join attribute. Obtaining all subcategories of a given category can be resolved in the following way.
SQLite
Most RDBMS provide a way to keep reference integrity through some event/trigger facility. This is not the case for SQLite. In order to keep reference integrity 2 special annotations are provided: @Cascade and @Nullable. This example illustrates the relationship between the User and Profile class.
When defining the User entity we'll append a @Cascade annotation to its profile attribute.
By using the @Cascade annotation we instruct the manager to delete the related profile once a user is removed from database.
But what if we don't want to remove a profile? In that case the userId attribute in the Profile class must include the @Nullable annotation. The manager checks if this annotation is present in the related entity in order to determine which action must be taken. When @Nullable is found then only an update query is executed.
Addtional options
One-To-Many and Many-To-Many associations support two additional configuration annotations:
- @Index: Indicates the attribute used for indexation.
- @OrderBy: Used to obtain a list ordered by the specified attribute.
For example, the subcategories association in the Category class could be redefined to obtain a list of categories indexed by name and ordered by id. We can achieve this with the following declaration:
Storing entities
The Manager class provides a save method which its pretty self explanatory.
This method can even store associated entities, which depending on the situation can save a bit of work.
When the primary key attribute of an entity is already set, the save method does an update query.
By default, this saves the entity along with all associated values. There are some scenarios though in which this behaviour is not necessary and could produce some unnecesary overhead. Let's take the Profile -> User association as an example.
Obtaining a profile in this way will also evaluate the user association defined in the Profile class. As explained before, saving this profile not only updates the profiles table but also the associated user. In order to reduce the number of queries to perform we'll set the association depth to 0 through the depth method.
When the association depth is set to 0 no related data is obtained. That means that doing something like:
will return a NULL value. The save method also expects a depth parameter (default to 1) that we can manipulate to define if related data must be updated along with the entity. This means that if the related data is not updated we can use the depth method to optimize the amount of data to obtain. Then, we can store the modified entity and add a second argument to avoid storing any associated data. The example above clarifies this process.
Dynamic SQL
Introduction
Queries could also contain logic expressions which are evaluated againts current arguments. These expressions (or S-expressions) are written in eMacros, a language based on lisphp. Dynamic expressions are included between the delimiters [? and ?]. The next example shows a query that sets the condition dynamically by checking the argument type.
eMacros 101
Just to give you a basic approach of how S-expressions work here's a list of small examples. Refer to eMacros documentation for more.
Configuration values
This example adds an ORDER clause if the configuration key 'order' is set.
Typed expressions
A value returned by a dynamic SQL expression can be associated to a type by adding the type identifier right after the first delimiter. This example simulates a search using the LIKE operator with the value returned by a dynamic expression that returns a string.
Dynamic Attributes
Introduction
Dynamic attributes provide us with an alternate method for fetching related data from an entity instance.
Queries
This example introduces a new entity class named Sale. A sale is related to a product by its productId property. Let's say that we want to obtain that product without declaring an association. In order to do this, we add a product property which includes a special @Query annotation. This annotation expects a string containing the query that solves this association.
When used along with @Query, the @Type annotation specifies the mapping expression to use.
Adding parameters
The @Param annotation can be used to define a list of arguments for a dynamic attribute. These arguments can be either a property of the current entity or a constant value. The annotation @Param(self) indicates that the current instance is used as first argument. If no additional parameters are added then it can be ignored. Knowing this we can redefine the product property in two ways.
Or the alternative syntax specifying the attribute name to use.
The next example adds a relatedProducts property in the Product class that includes 2 arguments: the current partial instance and a integer value that sets the amount of objects to return.
Statements
Statements provide a more generic way to obtain values by using a special syntax that includes an entity class plus a statement id separated by a dot. The statement id defines a search criteria according to a list of supported expressions. For example, User.findByPk obtains a User entity by primary key. The required argument is provided through the @Param annotation.
List of supported statements
Statement ID | Example | Arguments | Returns |
---|---|---|---|
findByPk | User.findByPk | 1 | Entity |
findAll | Product.findAll | None | List |
findBy{PROPERTY} |
User.findByEmail Product.findByCode |
1 | If PROPERTY is @Id or @Unique: Entity. A list otherwise. |
{PROPERTY}[Not]Equals |
Product.descriptionEquals User.emailNotEquals |
1 | Without NOT + PROPERTY is @Id or @Unique: Entity. A list otherwise. |
{PROPERTY}[Not][I]Contains |
Profile.surnameContains Product.codeICointains User.emailNotContains |
1 | List |
{PROPERTY}[Not][I]StartsWith |
Product.categoryStartsWith Product.categoryIStartsWith Profile.firstnameNotStartsWith |
1 | List |
{PROPERTY}[Not][I]EndsWith |
Product.categoryEndsWith Product.categoryIEndsWith Profile.firstnameNotEndsWith |
1 | List |
{PROPERTY}Is[Not]Null |
Product.descriptionIsNull User.lastLoginIsNotNull |
None | List |
{PROPERTY}[Not]GreaterThan[Equal] |
Product.idGreaterThan Product.priceGreaterThanEqual Product.priceNotGreaterThan |
1 | List |
{PROPERTY}[Not]LessThan[Equal] |
Product.idLessThan Product.priceLessThanEqual Product.priceNotLessThan |
1 | List |
{PROPERTY}[Not]Between |
Product.priceBetween Product.priceNotBetween |
2 | List |
{PROPERTY}[Not][I]Matches |
Product.categoryMatches Product.codeNotMatches Product.descriptionIMatches |
1 | List |
Stored procedures
In order to bind an attribute to a procedure execution we add a @Procedure annotation specifying its name. Additional arguments can be supplied through @Param.
Macros
@Eval evaluates a S-expression against current entity. This powerful feature can be used to obtain a person's age and fullname.
Conditional attributes
The @If and @IfNot annotations are used to define conditional attributes. These attributes are evaluated if the given expression evaluates to true with @If and false with @IfNot. Conditions must also be expressed as macros.
Additionally, the @IfNotNull annotation evaluates a dynamic attribute if the specified attribute is not null.
Options
The @Option annotation set a custom option value for the current attribute. An option must define its name by setting it between parentheses just before the value.
Cache
Introduction
eMapper provides value caching through SimpleCache, a small PHP library supporing Memcache adn APC cache providers. Before setting a cache provider make sure the required extension is correctly installed.
Providers
Storing values
Custom types
Introduction
Type handlers are classes that manage how a value is stored and retrieved from a column. The following code examples introduce a custom type RGBColor which is used to manage a color palette.
Type handlers
A type handler extends the eMapper\Type\TypeHandler class and implements the setParameter and getValue methods.
Types and aliases
Using custom types
Appendix I: Additional features
Query debugging
The debug method receives a Closure instance which can be used to inspect a query before being sent to the database.
This method is supported by fluent queries and manager instances as well.
Resultmaps
Resultmaps are a convenient way of mapping objects and arrays without relying on entities. We declare a resultmap as a class using the appropiate annotations for each property.
We instruct a mapper instance to use a resultmap by appending a call to the resultmap method. This method expects the resultmap class fullname as an argument.
Transactions
Mapper instances do support transactions through the methods beginTransaction, commit and rollback.
Appendix II: Annotations
Class annotations
Annotation | Description | Example |
---|---|---|
@Entity | Used to indicate that a class is an entity. Value must declare the associated table name. | @Entity users |
Property annotations
Annotation | Description | Example |
---|---|---|
@Id | Indicates that a property is a primary key. | - |
@Type | Indicates the type of the current property. | @Type string |
@Column | Indicates the column that is referenced by the property. | @Column user_id |
@Unique | Indicates that a property is unique. | - |
@Nullable | Indicates that a property can store NULL values. | - |
@ReadOnly | Indicates that a property is read-only and therefore will not be used for INSERT queries. | - |
@OnDuplicate | Checks if the value for the current property is already present. Value must indicate which action to take if the value is found, being "ignore" or "update" the possible options. | @OnDuplicate ignore |
Associations
Annotation | Description | Example |
---|---|---|
@OneToOne | Indicates that a property is a one-to-one association. Must indicate which entity class is referenced. Requires @Attr. | @OneToOne Profile |
@OneToMany | Indicates that a property is a one-to-many association. Must indicate which entity class is referenced. Requires @Attr. | @OneToMany Post |
@ManyToOne | Indicates that a property is a many-to-one association. Must indicate which entity class is referenced. Requires @Attr. | @ManyToOne Group |
@ManyToMany | Indicates that a property is a many-to-many association. Must indicate which entity class is referenced. Requires @Join. | @ManyToMany Product |
@Attr | Indicates which property is used for an association. If the property is declared within the same class then it must be expressed between parentheses. | @Attr(clientId) @Attr userId |
@Join | Indicates the join table used for a many-to-many association. It also must declare the column names used for joining, first being the one that references current entity. | @Join(user_id,task_id) users_tasks |
@Index | Indicates which property is used for indexation. | @Index productId |
@OrderBy | Indicates which property is used for order. Multiple annotations could be included. | @OrderBy price @OrderBy countryId ASC |
@Cache | Indicates if the associated value is stored in cache. Must include a string key. It also can include an amount of time between parentheses. | @Cache(120) USER_%{i} |
@Cascade | Indicates that associated values must be updated accordingly once an entity is deleted. Used to keep reference integrity in SQLite. | - |
Dynamic attributes
Annotation | Description | Example |
---|---|---|
@Query | Used to bind an attribute with the value returned by a query. | @Query "SELECT * FROM users" |
@Statement | Binds an attribute to a statement execution. Value must indicate the entity class and a statement id separated by a "." character. | @Statement Product.findByCode |
@Procedure | Binds an attribute to a stored procedure execution. | @Procedure Products_FindByCategory |
@Eval | Binds an attribute to a S-expression (macro). Does not support @Param. @Cacheable by default. | @Eval (. (#surname) ', ' (#firstname)) |
@Param | Adds an argument to a dynamic attribute. The special form @Param(self) indicates that the whole instance is used as an argument. Attribute values must be expressed between parentheses. | @Param 100 @Param(userId) |
@Type | Indicates the mapping expression for @Query, @Statement and @Procedure. | @Type int @Type obj:Acme\Vehicle[id] |
@Cache | Indicates the cache options for the given result. Must include a string key and the amount of time to keep in cache between parentheses. | @Cache(120) PRODUCT_%{s} |
@Option | Includes a custom option. Must indicate the option name between parentheses. | @Option(order) 'category' |
@Cacheable | Indicates that the value returned by a dynamic attribute can be stored in cache. | - |
@ArgTypes | Defines a procedure's argument types. Types should be expressed between parentheses. | @ArgTypes(s. i, date) |
@UsePrefix | Determines if the stored procedure name is declared using the database prefix | @UsePrefix true |
@ReturnSet | Only PostgreSQL. Tells procedure that a column set is returned. | @ReturnSet true |
@EscapeName | Only PostgreSQL. Escapes procedure name using quotes. | @EscapeName true |
@If, @IfNot, @IfNotNull | Used to indicate that a dynamic attribute must only be evaluated if a condition evaluates to true. @If and @IfNot evaluates a macro against current instance while @IfNotNull verifies that an attribute is not NULL. | @If (== (#category) 'laptops') @IfNot (== (#status) 'ready') @IfNotNull(userId) |
Appendix III: Configuration keys
Methods like type, indexCallback and resultmap define a list of internal options that are later interpreted by a Mapper instance. Here is the full list of these internal options along with their corresponding methods.
Option | Method | Description | Expected value |
---|---|---|---|
map.type | type | Sets mapping expression. | string |
map.params | type (second argument) | Additional mapping arguments (used only to define a column name when mapping to a simple type). | string |
map.result | resultmap | Sets the resultmap. | string |
callback.each | each | Sets a callback that iterates over the obtained results. | Closure |
callback.filter | filterCallback | Sets the filter callback to apply to the obtained results. | Closure |
callback.empty | emptyCallback | Sets the callback to execute if no results are found. | Closure |
callback.debug | debug | Sets the debugging callback. | Closure |
callback.index | indexCallback | Sets the indexation callback. | Closure |
callback.group | groupCallback | Sets the grouping callback. | Closure |
cache.key | cache | Sets the cache key. | string |
cache.ttl | cache (second argument) | Cache TTL (time to live) in seconds. | integer |
License
This code is licensed under the MIT license.
All versions of emapper with dependencies
omocha/omocha Version 1.1.*
emaphp/simplecache Version 1.0.*
emacros/emacros Version 1.1.*