An ActiveRecord implementation for PHP 5.
Designed to eventually offer as much of the functionality available in Ruby on Rails's ActiveRecord implementation as is feasible, this is a base class for building data models capable of storing and retrieving themselves from permanent storage (a database) and whose attributes are largely driven by the database schema as opposed to being defined in the class. That's a long-winded way of saying ActiveRecord is a fast way to create objects that mirror a database table.
Take, for example, a table created with the following SQL:
To create an active record class linked to this table, all you need to write is:
That's it. Your User class (note User is singular while the table users is plural) now automatically has properties named id, username, full_name, password_hash, disabled, and last_login. It also inherits convenient finder and save methods from the base ActiveRecord class and includes conveniences for easily updating an object with values submitted from a web form.
For those already familiar with the Rails implemenation, it's worth nothing several significant differences between it and this implementation. Most notably, methods wich apply to the table or class as a whole are not implemented as static methods. This is due to a number of limitations in PHP. Specifically, PHP does not provide any visibility about the scope in which it was called to static method. For example, let's say a static method foo is defined on a class A and a class B inherits from class A but does not definie it's own method foo. Within method foo you have no way of knowing whether it was invoked by calling A::foo() or B::foo, and that distinction is critical for any ActiveRecord implementation. For related reasons, it would also not possible for class B to override other static methods defined by A and have A call those methods. That is necessary if derived classes want to provide any class-specific behavior at the class or table level. Finally, PHP does not have facilities for true class-level metadata or behavior (there's no Class class that every object has an instance of, only a class name). At the time this code was being developed, PHP announced the availability of "late static bindings" beginning in version 5.3.0. While that may address some of the issues, it's still somewhat unclear how easy it would be for a base class to understand the calling scope when being called in the context of a derived class, and the desire to support as many 5.x versions of PHP as possible ruled that out as a viable alternative. So, in a nutshell, you'll need an instance of a class before doing any operations at all.
Example:
The other significant deviation from the approach used by rails is in the area of class initialization. Ruby allows more than just method and property definitions within the body of a class or module. It's possible to invoke methods and they in turn can affect the definition of your class or module. Of course, PHP doesn't have any kind of facility like that, which means we need another way to indicate things like use a different table name or primary key for this class. For this implementation, we introduce a method named init_class which is invoked only once during a script's lifecycle when the class should first be initialized. Other ActiveRecord implementations have used different approaches, including using attributes to indicate this information. We felt this approach provided for the most readable and easily documented code and also gives the most flexibility by allowing the execution of arbitrary code at startup.
For clarification, here's an example of defining a UserProfile class which maps to a table named users (instead of user_profiles, as would otherwise be assumed):
A more subtle, but important, difference from Rails is the handling of derived classes. Like Rails, this class implements single table inheritance. Let's say we have two classes:
Here, as in Rails, instances of both classes would be stored on a table named user. Which class each record was an instance of would be distinguished through the use of a type column, which would have the value 'User' for User instances and 'AdminUser' for AdminUser instances. Things get different, however, when additional abstract classes find their way into the mix. Take, for example:
In this case, both User and AdminUser objects would be stored in a table named myapp_users, not myapp_my_active_record. That's because this implementation ignores abstract base classes for the pupose of table naming. As shown here, that can be conveniently used to set policy which is inherited by derived classes. In Ruby this same effect can be achieved by redefining portions of the ActiveRecord::Base class, but, of course, that's much less easily achieved (if at all) in PHP.
The final important convention to note in this class is the naming convention used for mutator and accessor methods. The get method for a property named foo will have the signature foo(). The set method for a property named foo will have the signature set_foo(). This is helpful not only for tracking down the mutators and accessors for your favorite properties, but also for defining custom methods for accessing or setting properties created from database columns. Let's say, for example, I want to always return the last_login property for my User object as an instance of a custom objected named MyDateTime, and likewise I want to always accept an object of the same type when setting the property. I can easily set that up like so:
Note the use of the read_attribute and write_attribute methods here to gain access to the underlying object properties. Even though only these two methods have been declared, they will also be used anytime the corresponding public properties are accessed. Take for example these lines of code:
The first line will actually invoke the last_login() method, and the second will invoke the set_last_login() method. That's because the base ActiveRecord class actually maps all access to non-existent properties to the corresponding accessor or mutator method. The byproduct of this behavior is that many of the methods defined on the ActiveRecord class can also be accessed as though they were public properties.
Events
ActiveRecord supports the following event lifecycle:
Example:
Located in /activerecord/lib/ActiveRecord/Base.php (line 476)
Class | Description |
---|---|
Migration_Record |
Attributes
A collection of cached attribute values
Any errors that apply to this object
Cached logger
Class meta information
Indicates whether this is a new record or not
Indicates whether this record is read-only or not
Constructor
Accepts an optional associative array of attribute names and values as the initial values to set on the object.
Note that although this behavior is not very clearly explained in the PHP documentation, if derived classes do not specify a constructor, they will inherit this one by default.
Return a list of all attributes which have been made accessible to mass assignment by passing them to attr_accessible (the list is always maintained in sorted order).
Add conditions fragment to a SQL statement
Add an event listener to this class.
Add join fragment to a SQL statement
Add limit information to the SQL statement
Define a proxy for a method name.
A proxied method allows the addition of "virtual" methods to a class which are actually invoked on another object. This is useful for features such as associations which need to define additional methods dynamically.
Methods calls made to the other object include two additional parameters which are always the first two parameters passed. They are the ActiveRecord_Base instance the call is being invoked on and an ActiveRecord_Proxy object which allows direct access to the attributes for the object.
Example:
Add order by fragment to a SQL statement
Register a method on this class to be called after a save operation is performed for a new record.
Register a method on this class to be called after the record is destroyed.
Register a method on this class to be called after any save operation is performed.
Register a method on this class to be called after a save operation is performed for a record being updated (non-new record).
Register a method on this class to be called after any validation operation is performed.
Register a method on this class to be called after a validation operation is performed for a new record.
Register a method on this class to be called after a validation operation is performed for a record being updated (non-new record).
Create an options parameter for passing to a find_* method using the output from create_finder_attribute_hash
Returns this object's attributes as an associative array.
Return the list of column names and attribute values for use in the SET clause of an UPDATE statement. The primary key column is excluded.
Return an associative array of attributes representing the default values for the fields on this class's table. The array keys are the field names and the values are the default values.
Return a list of attributes which are never permitted in mass-assignment. The returned list must be sorted. By default, the returned list include the primary key and inheritance column.
Returns an array of names for the attributes available on this object. The returned list is sorted alphabetically.
Returns true if the named attribute exists for this object and has a non-empty value (not null, false, 0, the empty string, or any empty array).
Accepts the names of one or more attributes which are allowed to
be assigned through mass assignment in this class (assignment through the constructor, create method, set_attributes method, etc.). If this method is used, only those attributes specified as accessible are allowed to be assigned through mass assignment.
Accepts the names of one or more attributes which are protected from mass assignment in this class (assignment through the constructor, create method, set_attributes method, etc.).
Traverses the list of this class's parents to return the oldest ancestor which is not abstract (this is for single table inheritance since inherited classes uses the base class's table.
Register a method on this class to be called before a save operation is performed for a new record.
Register a method on this class to be called before the record is destroyed.
Register a method on this class to be called before any save operation is performed.
Register a method on this class to be called before a save operation is performed for a record being updated (non-new record).
Register a method on this class to be called before any validation operation is performed.
Register a method on this class to be called before a validation operation is performed for a new record.
Register a method on this class to be called before a validation operation is performed for a record being updated (non-new record).
Specifies a one to one association with another class where the foreign key resides in this class.
For example:
Implies a table structure like:
A belongs_to association adds four methods to the class:
Turn a table name back into a class name. This follows the reverse rules of the table_name method. So, for example, "my_objects" becomes "MyObject".
Return an array of ActiveRecord_Column objects for the table associated with this class.
Return the list of all attribute names prepared for use in an insert statement
Returns an associative array of column objects keyed by column name for the table associated with this class.
Returns the column object for the named attribute
Returns an array of column names as strings.
Return the database connection for this class
Returns an array of column objects suitable for direct editing
by a user. The primary key; the inheritance column; all columns ending in _id or _count; and any columns named created_at, created_on, updated_at, updated_on, or lock version are omitted from the returned list.
Returns a count of records matching the provided criteria.
Accepted options are the same as for find_all().
Example
Returns the result of an SQL statement that should only include a COUNT(*) -- or equivalent -- in the SELECT clause.
Example
Create and save (validation permitting) an object. The newly created object is returned. If validation fails, the unsaved object is still returned.
Example:
Extract attribute names from an "_and_" separated string and construct an associative array using a corresponding array of arguments.
Like create, but calls save_or_fail, so if validation fails, an exception is thrown.
Handles performing the correct save operation for the object.
Create a new record in the database for this object
Subtracts one from the value of the named attribute. If the attribute is null, it is first initialized to zero before subtracting one.
Decrements the named attribute and saves the object.
Similar to increment_counter, but decrements the specified counter instead.
This example decrements the in_stock field for the Product with id 204:
Deletes the record with the given id (or records with the given
ids). The record is not instantiated first, so no callbacks are triggered. As with find, this method accepts one or more arguments. Any arrays which are passed are assumed to be an array of ids.
Destroys all records that match the given conditions. The
records are not instantiated first, and, as such, no callbacks are triggered. As with the conditions option for a find* method, the conditions argument is a SQL fragment suitable for use in a where clause. The conditions parameter may be a string or an array with the first elementing containing the SQL fragment and the remaining containing parameters.
Returns a count of records that were deleted.
Examples:
delete_cached_attribute removes a temporary value previously set with write_cached_attribute
Delete the record from the database
Destroys the objects for a set of records that match the given
conditions. As with the conditions option for a find* method, the conditions argument is a SQL fragment suitable for use in a where clause. The conditions parameter may be a string or an array with the first elementing containing the SQL fragment and the remaining containing parameters.
Examples:
Sets the inheritance column value for this object if needed.
Accepts an id or set of conditions and returns true if a
matching record exists, otherwise it returns false. If the test parameter is an array, it is assumed to contain conditions in the same format used with the 'conditions' option for find. Anything else is assumed to be an id.
Example:
Find one or more records with the specified id(s). This function accepts one or more ids as it's argument. If an array is passed, it is assumed to be a list of ids.
Returns the record or records with the given ids. In the case of a single id, a single object is returned. In the case of multiple ids, an array of objects is returned. The count of objects found must match the count of ids or an exception is thrown.
Example:
Retrieve all records matching the provided criteria.
Options are:
Example:
Retrieve records by a raw SQL statement instead of through the normal find helper methods.
Returns an array containing all matching records found. If no matches are found, an empty array is returned.
Example:
Retrieve the first record matching the provided criteria.
Options are:
Example:
Notifies any registered event listeners
Called internally to access the class-level meta information
Returns true if this object has the named attribute (even if it is empty)
Returns true if this object has a cached attribute with the provided name. See write_cached_attribute for more information on cached attributes.
Specifies a one to many association with another class where the foreign key resides in the other class.
For example:
Implies a table structure like:
A has_many association adds four methods to the class:
Specifies a one to one association with another class where the foreign key resides in the other class.
For example:
Implies a table structure like:
A has_one association adds four methods to the class:
The id method/property always accesses the primary key column, even if the primary key is named something else.
Access the value of the primary key column before the type cast
Adds one to the value of the named attribute. If the attribute is null, it is first initialized to zero before adding one.
Increments the named attribute and saves the object.
Increments the specified counter for the given id by one.
This example increments the hits field for the Document with id 204:
Return the name of the column used for single table inheritance.
Called to initialize class-specific information such as associations, validations, etc. Derived classes should implement this method to specify these class-specific details.
Create a new instance of an object from a record. This handles single table inheritance, allowing different types of objects to be instantiated from the same table.
Determine if this class is the first concrete descendent from ActiveRecord_Base.
Determine if this record is valid
This is invoked automatically by ActiveRecord_InfoMgr when the class-level meta information should be loaded.
Return this class's logger
A convenience method that calls Support_Util::model()
This method exists purely for more concise code creation. All three of the examples below perform the same operation:
Returns true if a corresponding record does not yet exist for this object in the database (a new object which has not yet been saved).
Return the name of this class's primary key. Default is 'id'.
Handle include processing for a result set
Return a list of all attributes which have been protected from mass assignment (the list is always maintained in sorted order).
Return a proxy object for this class.
Proxies are used by external classes which dynamically extend the functionality of an ActiveRecord class.
Return the proxy method for a given method name, or null if none declared.
Returns the setting of the read-only flag for this object
Returns the value of an attribute type cast to the correct data type.
Returns the value of an attribute without performing any type casting.
Return the value stored in the attribute cache for the given name.
If no value is in the cache, returns null. Note that a null value does not indicate the attribute has no value, only that no cached value is present. See write_cached_attribute for more information on cached attributes.
Reloads the attributes for this object from the database.
Remove attributes protected from mass assignment from an associative array
Remove an event listener from this class.
Type cast a value from PHP code into a value suitable for use in
SQL. By default, this method just delegates to the column definition, but, like type_cast, it exists as a hook for derived classes to be able to extend the column definition's behavior on an application-wide basis. For example, if you have a different class you use to represent dates, or another data type for enums in MySQL. Note that this is not the correct place to handle field-specific type casts for an individual field (for example, if the 'foo' field for class 'Bar' is stored as a string but has a wrapper class that is used in PHP code). Overrides for individual fields are best handled by defining accessor and mutator (get/set) methods for those fields.
Evaluate all validations of a given type associated with this object
Save the object to the database. If no record yet exists for the object, a new one is created. Otherwise, the existing record is updated.
Saves the object to the database. If the save operation does not succeed, a RecordNotSaved exception is thrown.
Invoke a named method on an blank instance of our parent class if our parent is not abstract.
Return the name of this class's sequence. This function may be overridden by derived classes. The default name is actually determined by providing the table name and primary key name to the connection. It then builds an appropriate sequence name.
Allows setting of multiple attributes at once by passing an associative array. The array keys are the attribute names and the array values are the values to assign.
Sets the database connection for this class
Set the value of the primary key
Set the name of the column used for single table inheritance.
Set the field name used as this class's primary key.
Change the setting of the read-only flag for this object
Set the name of this class's sequence.
Set the name of this class's table.
Set the prefix to add to the table name
Set the suffix to add to the table name
Determine whether the table associated with this class exists or not.
Return the name of this class's table. This function may be overridden by derived classes. By default it infers the name of the table by converting the mixed case name of the class to an underscore format and pluralizing the name.
Return any prefix to add to the table name
Return any suffix to add to the table name
Sets an attribute with a true value to false and anything else to true.
Toggles the named attribute and saves the object.
Type cast a raw column value to a value suitable for use in PHP
code. By default, this method just delegates to the column definition, but it exists as a hook for derived classes to be able to extend the column definition's behavior on an application-wide basis. For example, if you have a different class you want to use to represent dates, or another data type for enums in MySQL. Note that this is not the correct place to handle field-specific type casts for an individual field (for example, if the 'foo' field for class 'Bar' is stored as a string but has a wrapper class that is used in PHP code). Overrides for individual fields are best handled by defining accessor and mutator (get/set) methods for those fields.
Return condition SQL fragment for single table inheritance
Note: this is considerably less robust than the Rails active record implementation. Rails includes subclasses, whereas this does not. Reason being that it is not only cumbersome to determine descendents in PHP, but it would only ever be practical to determine descendents which have already been loaded/declared. This is only a factor if you have more than two levels of inheritance (in which case it would be a good idea to override this).
Find an object by id and update the attributes specified in an associative array. The object is automatically saved (validation permitting) and is then returned.
Multiple objects may updated at once by passing an array of ids and an array of attribute arrays. In that case, an array of updated objects is returned.
Example:
Updates a set of records given a SQL fragment for use inside a
SET clause and an optional set of conditions. As with the conditions option for a find* method, the conditions argument is a SQL fragment suitable for use in a where clause. Either parameter may be a string or an array with the first element containing the SQL fragment and the remaining containing parameters.
Returns a count of records that were updated.
Examples:
Updates a single attribute on this object and then saves the object.
Updates a list of attriubtes from an associative array and then saves the object.
Operates the same as update_attributes(), but calls save_or_fail, so a RecordNotSaved exception is thrown if the save operation does not succeed.
Update the record associated with the object
Perform validation checks applicable any time the record is saved. Use errors()->add($attribute, $message) to record any errors.
Add one or more validations for fields which have a confirmation field that must contain an identical value.
Example
Options include:
Add a validation for a field that is allowed to have any value not in a given list.
Example
Options include:
Add a validation for the format of a field. The format is validated using a perl-compatible regular expression.
Example
Options include:
Add a validation for a field that is only allowed to have a value in a given list.
Example
Options include:
Add one or more validations for fields lengths. Length refers to the number of characters in the field (string length).
Example
Options include:
Add a validation for a field that must be numeric.
Example
Options include:
Add one or more validations for fields which may not be empty.
Example
Options include:
Add one or more validations for fields which must be unique accross records.
Example
Options include:
Validates the options provided to a find method
Perform validation checks applicable only before saving a new record. Use errors()->add($attribute, $message) to record any errors.
Perform validation checks applicable only before saving an existing record. Use errors()->add($attribute, $message) to record any errors.
Validates a provided set of options against an allowed set
Convert a string name of a validation type to the class constant
Return the list of all attribute values prepared for use in an insert statement
Set the value of a named attribute. Empty strings for numeric fields are treated as NULL.
write_cached_attribute allows the object to store a temporary value
in an attribute cache. Values placed in the cache are ignored when saving or serializing the object. The cache is intended as a place to store attribute values that have been type cast when casting is an expensive operation and for storing dummy attributes (such as a password confirmation field) which are not persisted. The cache is never checked by the default attribute read mechanism. It is the responsibility of the object to implement any logic related to retrieving cached values as the correct time.
Allow dynamic use of accessor and mutator methods for column values. Accessors follow the format column_name() and mutators follow the format set_column_name($arg).
Also enables dynamic finder methods. Dynamic finder methods come in a variety of flavors:
Example:
Allow reading of attributes and reader methods as properties
Allow use of isset with record attributes as though they were public properties. Unlike other access to properties, this will not defer to to existing reader methods, only actual record properties can be tested.
Allow writing of attributes and use of set methods as properties
Discard meta data and database connections not specific to this
instance prior to serialization
Allow use of unset with record attributes as though they were public properties. Unlike other access to properties, this will not defer to to existing set methods, only actual record properties can be unset.
This has the effect of setting any existing property on the object to NULL.
Documentation generated on Wed, 25 Apr 2012 09:46:40 -0700 by phpDocumentor 1.4.3