/lib/AkActiveRecord.php
PHP | 5018 lines | 3286 code | 556 blank | 1176 comment | 580 complexity | 47070d1ff38415bf2a32501b1638610c MD5 | raw file
Possible License(s): LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- <?php
- /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
- // +----------------------------------------------------------------------+
- // | Akelos Framework - http://www.akelos.org |
- // +----------------------------------------------------------------------+
- // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
- // | Released under the GNU Lesser General Public License, see LICENSE.txt|
- // +----------------------------------------------------------------------+
- /**
- * @package AkelosFramework
- * @subpackage AkActiveRecord
- * @author Bermi Ferrer <bermi a.t akelos c.om> 2004 - 2007
- * @author Kaste 2007
- * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
- * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
- */
- require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkAssociatedActiveRecord.php');
- // Akelos args is a short way to call functions that is only intended for fast prototyping
- defined('AK_ENABLE_AKELOS_ARGS') ? null : define('AK_ENABLE_AKELOS_ARGS', false);
- // Use setColumnName if available when using set('column_name', $value);
- defined('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT') ? null : define('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT', true);
- defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS', true);
- defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS', true);
- defined('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE') ? null : define('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE', AK_ENVIRONMENT != 'testing');
- defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA', AK_ACTIVE_RECORD_ENABLE_PERSISTENCE && AK_ENVIRONMENT != 'development');
- defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE', 300);
- defined('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES') ? null : define('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES', true);
- defined('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS') ? null : define('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS', false);
- defined('AK_NOT_EMPTY_REGULAR_EXPRESSION') ? null : define('AK_NOT_EMPTY_REGULAR_EXPRESSION','/.+/');
- defined('AK_EMAIL_REGULAR_EXPRESSION') ? null : define('AK_EMAIL_REGULAR_EXPRESSION',"/^([a-z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-z0-9\-]+\.)+))([a-z]{2,4}|[0-9]{1,3})(\]?)$/i");
- defined('AK_NUMBER_REGULAR_EXPRESSION') ? null : define('AK_NUMBER_REGULAR_EXPRESSION',"/^[0-9]+$/");
- defined('AK_PHONE_REGULAR_EXPRESSION') ? null : define('AK_PHONE_REGULAR_EXPRESSION',"/^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/");
- defined('AK_DATE_REGULAR_EXPRESSION') ? null : define('AK_DATE_REGULAR_EXPRESSION',"/^(([0-9]{1,2}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{2,4})|([0-9]{2,4}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{1,2})){1}$/");
- defined('AK_IP4_REGULAR_EXPRESSION') ? null : define('AK_IP4_REGULAR_EXPRESSION',"/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/");
- defined('AK_POST_CODE_REGULAR_EXPRESSION') ? null : define('AK_POST_CODE_REGULAR_EXPRESSION',"/^[0-9A-Za-z -]{2,9}$/");
- // Forces loading database schema on every call
- if(AK_DEV_MODE && isset($_SESSION['__activeRecordColumnsSettingsCache'])){
- unset($_SESSION['__activeRecordColumnsSettingsCache']);
- }
- ak_compat('array_combine');
- /**
- * Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
- * which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
- * is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
- * database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
- *
- * See the mapping rules in table_name and the full example in README.txt for more insight.
- *
- * == Creation ==
- *
- * Active Records accepts constructor parameters either in an array or as a list of parameters in a specific format. The array method is especially useful when
- * you're receiving the data from somewhere else, like a HTTP request. It works like this:
- *
- * $user = new User(array('name' => 'David', 'occupation' => 'Code Artist'));
- * echo $user->name; // Will print "David"
- *
- * You can also use a parameter list initialization.:
- *
- * $user = new User('name->', 'David', 'occupation->', 'Code Artist');
- *
- * And of course you can just create a bare object and specify the attributes after the fact:
- *
- * $user = new User();
- * $user->name = 'David';
- * $user->occupation = 'Code Artist';
- *
- * == Conditions ==
- *
- * Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
- * The array form is to be used when the condition input is tainted and requires sanitization. The string form can
- * be used for statements that doesn't involve tainted data. Examples:
- *
- * class User extends AkActiveRecord
- * {
- * function authenticateUnsafely($user_name, $password)
- * {
- * return findFirst("user_name = '$user_name' AND password = '$password'");
- * }
- *
- * function authenticateSafely($user_name, $password)
- * {
- * return findFirst("user_name = ? AND password = ?", $user_name, $password);
- * }
- * }
- *
- * The <tt>authenticateUnsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
- * attacks if the <tt>$user_name</tt> and <tt>$password</tt> parameters come directly from a HTTP request. The <tt>authenticateSafely</tt> method,
- * on the other hand, will sanitize the <tt>$user_name</tt> and <tt>$password</tt> before inserting them in the query, which will ensure that
- * an attacker can't escape the query and fake the login (or worse).
- *
- * When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
- * question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
- * the question marks with symbols and supplying a hash with values for the matching symbol keys:
- *
- * $Company->findFirst(
- * "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
- * array(':id' => 3, ':name' => "37signals", ':division' => "First", ':accounting_date' => '2005-01-01')
- * );
- *
- * == Accessing attributes before they have been type casted ==
- *
- * Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
- * That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
- * has a balance attribute, you can call $Account->balance_before_type_cast or $Account->id_before_type_cast.
- *
- * This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
- * the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
- * want.
- *
- * == Saving arrays, hashes, and other non-mappable objects in text columns ==
- *
- * Active Record can serialize any object in text columns. To do so, you must specify this with by setting the attribute serialize whith
- * a comma separated list of columns or an array.
- * This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
- *
- * class User extends AkActiveRecord
- * {
- * var $serialize = 'preferences';
- * }
- *
- * $User = new User(array('preferences'=>array("background" => "black", "display" => 'large')));
- * $User->find($user_id);
- * $User->preferences // array("background" => "black", "display" => 'large')
- *
- * == Single table inheritance ==
- *
- * Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
- * by overwriting <tt>AkActiveRecord->_inheritanceColumn</tt>). This means that an inheritance looking like this:
- *
- * class Company extends AkActiveRecord{}
- * class Firm extends Company{}
- * class Client extends Company{}
- * class PriorityClient extends Client{}
- *
- * When you do $Firm->create('name =>', "akelos"), this record will be saved in the companies table with type = "Firm". You can then
- * fetch this row again using $Company->find('first', "name = '37signals'") and it will return a Firm object.
- *
- * If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
- * like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
- *
- * Note, all the attributes for all the cases are kept in the same table. Read more:
- * http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
- *
- * == Connection to multiple databases in different models ==
- *
- * Connections are usually created through AkActiveRecord->establishConnection and retrieved by AkActiveRecord->connection.
- * All classes inheriting from AkActiveRecord will use this connection. But you can also set a class-specific connection.
- * For example, if $Course is a AkActiveRecord, but resides in a different database you can just say $Course->establishConnection
- * and $Course and all its subclasses will use this connection instead.
- *
- * Active Records will automatically record creation and/or update timestamps of database objects
- * if fields of the names created_at/created_on or updated_at/updated_on are present.
- * Date only: created_on, updated_on
- * Date and time: created_at, updated_at
- *
- * This behavior can be turned off by setting <tt>$this->_recordTimestamps = false</tt>.
- */
- class AkActiveRecord extends AkAssociatedActiveRecord
- {
- //var $disableAutomatedAssociationLoading = true;
- var $_tableName;
- var $_db;
- var $_newRecord;
- var $_freeze;
- var $_dataDictionary;
- var $_primaryKey;
- var $_inheritanceColumn;
- var $_associations;
- var $_internationalize;
- var $_errors = array();
- var $_attributes = array();
- var $_protectedAttributes = array();
- var $_accessibleAttributes = array();
- var $_recordTimestamps = true;
- // Column description
- var $_columnNames = array();
- // Array of column objects for the table associated with this class.
- var $_columns = array();
- // Columns that can be edited/viewed
- var $_contentColumns = array();
- // Methods that will be dinamically loaded for the model (EXPERIMENTAL) This pretends to generate something similar to Ruby on Rails finders.
- // If you set array('findOneByUsernameAndPassword', 'findByCompany', 'findAllByExipringDate')
- // You'll get $User->findOneByUsernameAndPassword('admin', 'pass');
- var $_dynamicMethods = false;
- var $_combinedAttributes = array();
- var $_BlobQueryStack = null;
- var $_automated_max_length_validator = true;
- var $_automated_validators_enabled = true;
- var $_automated_not_null_validator = false;
- var $_set_default_attribute_values_automatically = true;
- // This is needed for enabling support for static active record instantation under php
- var $_activeRecordHasBeenInstantiated = true;
- var $__ActsLikeAttributes = array();
- var $__coreActsLikeAttributes = array('nested_set', 'list', 'tree');
- /**
- * Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
- */
- var $_defaultErrorMessages = array(
- 'inclusion' => "is not included in the list",
- 'exclusion' => "is reserved",
- 'invalid' => "is invalid",
- 'confirmation' => "doesn't match confirmation",
- 'accepted' => "must be accepted",
- 'empty' => "can't be empty",
- 'blank' => "can't be blank",
- 'too_long' => "is too long (max is %d characters)",
- 'too_short' => "is too short (min is %d characters)",
- 'wrong_length' => "is the wrong length (should be %d characters)",
- 'taken' => "has already been taken",
- 'not_a_number' => "is not a number"
- );
- var $__activeRecordObject = true;
- function __construct()
- {
- $attributes = (array)func_get_args();
- return $this->init($attributes);
- }
- function init($attributes = array())
- {
- AK_LOG_EVENTS ? ($this->Logger =& Ak::getLogger()) : null;
-
- $this->_internationalize = is_null($this->_internationalize) && AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT ? count($this->getAvaliableLocales()) > 1 : $this->_internationalize;
- @$this->_instatiateDefaultObserver();
- $this->setConnection();
- if(!empty($this->table_name)){
- $this->setTableName($this->table_name);
- }
- $this->_loadActAsBehaviours();
- if(!empty($this->combined_attributes)){
- foreach ($this->combined_attributes as $combined_attribute){
- $this->addCombinedAttributeConfiguration($combined_attribute);
- }
- }
- if(isset($attributes[0]) && is_array($attributes[0]) && count($attributes) === 1){
- $attributes = $attributes[0];
- $this->_newRecord = true;
- }
- // new AkActiveRecord(23); //Returns object with primary key 23
- if(isset($attributes[0]) && count($attributes) === 1 && $attributes[0] > 0){
- $record = $this->find($attributes[0]);
- if(!$record){
- return false;
- }else {
- $this->setAttributes($record->getAttributes(), true);
- }
- // This option is only used internally for loading found objects
- }elseif(isset($attributes[0]) && isset($attributes[1]) && $attributes[0] == 'attributes' && is_array($attributes[1])){
- foreach($attributes[1] as $k=>$v){
- $attributes[1][$k] = $this->castAttributeFromDatabase($k, $v);
- }
- $avoid_loading_associations = isset($attributes[1]['load_associations']) ? false : !empty($this->disableAutomatedAssociationLoading);
- $this->setAttributes($attributes[1], true);
- }else{
- $this->newRecord($attributes);
- }
- $this->_buildFinders();
- empty($avoid_loading_associations) ? $this->loadAssociations() : null;
- }
- function __destruct()
- {
- }
- /**
- * If this macro is used, only those attributed named in it will be accessible
- * for mass-assignment, such as new ModelName($attributes) and $this->attributes($attributes).
- * This is the more conservative choice for mass-assignment protection.
- * If you'd rather start from an all-open default and restrict attributes as needed,
- * have a look at AkActiveRecord::setProtectedAttributes().
- */
- function setAccessibleAttributes()
- {
- $args = func_get_args();
- $this->_accessibleAttributes = array_unique(array_merge((array)$this->_accessibleAttributes, $args));
- }
- /**
- * Attributes named in this macro are protected from mass-assignment, such as
- * new ModelName($attributes) and $this->attributes(attributes). Their assignment
- * will simply be ignored. Instead, you can use the direct writer methods to do assignment.
- * This is meant to protect sensitive attributes to be overwritten by URL/form hackers.
- *
- * Example:
- * <code>
- * class Customer extends AkActiveRecord
- * {
- * function Customer()
- * {
- * $this->setProtectedAttributes('credit_rating');
- * }
- * }
- *
- * $Customer = new Customer('name' => 'David', 'credit_rating' => 'Excellent');
- * $Customer->credit_rating // => null
- * $Customer->attributes(array('description' => 'Jolly fellow', 'credit_rating' => 'Superb'));
- * $Customer->credit_rating // => null
- *
- * $Customer->credit_rating = 'Average'
- * $Customer->credit_rating // => 'Average'
- * </code>
- */
- function setProtectedAttributes()
- {
- $args = func_get_args();
- $this->_protectedAttributes = array_unique(array_merge((array)$this->_protectedAttributes, $args));
- }
- /**
- * Returns true if a connection that?s accessible to this class have already been opened.
- */
- function isConnected()
- {
- return isset($this->_db);
- }
- /**
- * Returns the connection currently associated with the class. This can also be used to
- * "borrow" the connection to do database work unrelated to any of the specific Active Records.
- */
- function &getConnection()
- {
- return $this->_db;
- }
- /**
- * Set the connection for the class.
- */
- function setConnection($dns = null, $connection_id = null)
- {
- $this->_db =& Ak::db($dns, $connection_id);
- }
- /**
- * Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
- * and columns used for single table inheritance has been removed.
- */
- function getContentColumns()
- {
- $inheritance_column = $this->getInheritanceColumn();
- $columns = $this->getColumns();
- foreach ($columns as $name=>$details){
- if((substr($name,-3) == '_id' || substr($name,-6) == '_count') ||
- !empty($details['primaryKey']) || ($inheritance_column !== false && $inheritance_column == $name)){
- unset($columns[$name]);
- }
- }
- return $columns;
- }
- /**
- * Creates an object, instantly saves it as a record (if the validation permits it), and returns it.
- * If the save fail under validations, the unsaved object is still returned.
- */
- function &create($attributes = null)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- if(func_num_args() > 1){
- $attributes = func_get_args();
- }
- $model = $this->getModelName();
- $object =& new $model();
- $object->setAttributes($attributes);
- $object->save();
- return $object;
- }
- /**
- * Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
- *
- * $Product->countBySql("SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id");
- */
- function countBySql($sql)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- if(!stristr($sql, 'COUNT') && stristr($sql, ' FROM ')){
- $sql = 'SELECT COUNT(*) '.substr($sql,strpos(str_replace(' from ',' FROM ', $sql),' FROM '));
- }
- if(!$this->isConnected()){
- $this->setConnection();
- }
-
- AK_LOG_EVENTS ? ($this->Logger->message($this->getModelName().' executing SQL: '.$sql)) : null;
- $rs = $this->_db->Execute($sql);
- return @(integer)$rs->fields[0];
- }
- /**
- * Increments the specified counter by one. So $DiscussionBoard->incrementCounter("post_count",
- * $discussion_board_id); would increment the "post_count" counter on the board responding to
- * $discussion_board_id. This is used for caching aggregate values, so that they doesn't need to
- * be computed every time. Especially important for looping over a collection where each element
- * require a number of aggregate values. Like the $DiscussionBoard that needs to list both the number of posts and comments.
- */
- function incrementCounter($counter_name, $id, $difference = 1)
- {
- $new_value = $this->getAttribute($counter_name) + $difference;
- if($this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){
- return false;
- }
- return $new_value;
- }
- /**
- * Works like AkActiveRecord::incrementCounter, but decrements instead.
- */
- function decrementCounter($counter_name, $id, $difference = 1)
- {
- $new_value = $this->getAttribute($counter_name) - $difference;
- if(!$this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){
- return false;
- }
- return $new_value;
- }
- /**
- * Finds the record from the passed id, instantly saves it with the passed attributes (if the validation permits it),
- * and returns it. If the save fail under validations, the unsaved object is still returned.
- */
- function update($id, $attributes)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- if(is_array($id)){
- $results = array();
- foreach ($id as $idx=>$single_id){
- $results[] = $this->update($single_id, isset($attributes[$idx]) ? $attributes[$idx] : $attributes);
- }
- return $results;
- }else{
- $object =& $this->find($id);
- $object->updateAttributes($attributes);
- return $object;
- }
- }
- /**
- * Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
- * Note: Make sure that updates made with this method doesn't get subjected to validation checks.
- * Hence, attributes can be updated even if the full object isn't valid.
- */
- function updateAttribute($name, $value)
- {
- $this->setAttribute($name, $value);
- return $this->save(false);
- }
- /**
- * Updates all the attributes in from the passed array and saves the record. If the object is
- * invalid, the saving will fail and false will be returned.
- */
- function updateAttributes($attributes, $object = null)
- {
- isset($object) ? $object->setAttributes($attributes) : $this->setAttributes($attributes);
- return isset($object) ? $object->save() : $this->save();
- }
- /**
- * Updates all records with the SET-part of an SQL update statement in updates and returns an
- * integer with the number of rows updates. A subset of the records can be selected by specifying conditions. Example:
- * <code>$Billing->updateAll("category = 'authorized', approved = 1", "author = 'David'");</code>
- *
- * Important note: Condifitons are not sanitized yet so beware of accepting
- * variable conditions when using this function
- */
- function updateAll($updates, $conditions = null)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- /**
- * @todo sanitize sql conditions
- */
- $sql = 'UPDATE '.$this->getTableName().' SET '.$updates;
- $sql .= isset($conditions) ? ' WHERE '.$conditions : '';
- $this->_executeSql($sql);
- return $this->_db->Affected_Rows();
- }
- /**
- * Deletes the record with the given id without instantiating an object first. If an array of
- * ids is provided, all of them are deleted.
- */
- function delete($id)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $id = func_num_args() > 1 ? func_get_args() : $id;
- return $this->deleteAll($this->getPrimaryKey().' IN ('.(is_array($id) ? join(', ',$id) : $id).')');
- }
- /**
- * Returns an array of names for the attributes available on this object sorted alphabetically.
- */
- function getAttributeNames()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $attributes = array_keys($this->getAvailableAttributes());
- $names = array_combine($attributes,array_map(array(&$this,'getAttributeCaption'), $attributes));
- natsort($names);
- return $names;
- }
- /**
- * Returns true if the specified attribute has been set by the user or by a database load and is neither null nor empty?
- */
- function isAttributePresent($attribute)
- {
- $value = $this->getAttribute($attribute);
- return !empty($value);
- }
- /**
- * Deletes all the records that matches the condition without instantiating the objects first
- * (and hence not calling the destroy method). Example:
- *
- * <code>$Post->destroyAll("person_id = 5 AND (category = 'Something' OR category = 'Else')");</code>
- *
- * Important note: Condifitons are not sanitized yet so beware of accepting
- * variable conditions when using this function
- */
- function deleteAll($conditions = null)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- /**
- * @todo sanitize sql conditions
- */
- $sql = 'DELETE FROM '.$this->getTableName();
- $sql .= isset($conditions) ? ' WHERE '.$conditions : ($this->_getDatabaseType() == 'sqlite' ? ' WHERE 1' : ''); // (HACK) If where clause is not included sqlite_changes will not get the right result
- $this->_executeSql($sql);
- return $this->_db->Affected_Rows() > 0;
- }
- /**
- * Destroys the record with the given id by instantiating the object and calling destroy
- * (all the callbacks are the triggered). If an array of ids is provided, all of them are destroyed.
- * Deletes the record in the database and freezes this instance to reflect that no changes should be
- * made (since they can't be persisted).
- */
- function destroy($id = null)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $this->transactionStart();
- $id = func_num_args() > 1 ? func_get_args() : $id;
- if(isset($id)){
- $id_arr = is_array($id) ? $id : array($id);
- if($objects = $this->find($id_arr)){
- $results = count($objects);
- $no_problems = true;
- for ($i=0; $results > $i; $i++){
- if(!$objects[$i]->destroy()){
- $no_problems = false;
- }
- }
- $this->transactionComplete();
- return $no_problems;
- }else {
- $this->transactionComplete();
- return false;
- }
- }else{
- if(!$this->isNewRecord()){
- if($this->beforeDestroy()){
- $this->notifyObservers('beforeDestroy');
- $sql = 'DELETE FROM '.$this->getTableName().' WHERE '.$this->getPrimaryKey().' = '.$this->_db->qstr($this->getId());
- $this->_executeSql($sql);
- $had_success = ($this->_db->Affected_Rows() > 0);
- if(!$had_success || ($had_success && !$this->afterDestroy())){
- $this->transactionFail();
- $had_success = false;
- }else{
- $had_success = $this->notifyObservers('afterDestroy') === false ? false : true;
- }
- $this->transactionComplete();
- $this->freeze();
- return $had_success;
- }else {
- $this->transactionFail();
- $this->transactionComplete();
- return false;
- }
- }
- }
- if(!$this->afterDestroy()){
- $this->transactionFail();
- }else{
- $this->notifyObservers('afterDestroy');
- }
- $this->transactionComplete();
- return false;
- }
- /**
- * Destroys the objects for all the records that matches the condition by instantiating
- * each object and calling the destroy method.
- *
- * Example:
- *
- * $Person->destroyAll("last_login < '2004-04-04'");
- */
- function destroyAll($conditions)
- {
- if($objects = $this->find('all',array('conditions'=>$conditions))){
- $results = count($objects);
- $no_problems = true;
- for ($i=0; $results > $i; $i++){
- if(!$objects[$i]->destroy()){
- $no_problems = false;
- }
- }
- return $no_problems;
- }else {
- return false;
- }
- }
- /**
- * Establishes the connection to the database. Accepts an array as input where the 'adapter'
- * key must be specified with the name of a database adapter (in lower-case) example for regular
- * databases (MySQL, Postgresql, etc):
- *
- * $AkActiveRecord->establishConnection(
- * array(
- * 'adapter' => "mysql",
- * 'host' => "localhost",
- * 'username' => "myuser",
- * 'password' => "mypass",
- * 'database' => "somedatabase"
- * ));
- *
- * Example for SQLite database:
- *
- * $AkActiveRecord->establishConnection(
- * array(
- * 'adapter' => "sqlite",
- * 'dbfile' => "path/to/dbfile"
- * )
- * )
- */
- function &establishConnection($spec = null)
- {
- if(isset($spec)){
- $dns = is_string($spec) ? $spec : '';
- if(!empty($spec['adapter'])){
- $dsn = $spec['adapter'] == 'sqlite' ?
- 'sqlite://'.urlencode($spec['dbfile']).'/' :
- $spec['adapter'].'://'.@$spec['username'].':'.@$spec['password'].'@'.@$spec['host'].'/'.@$spec['database'];
- }
- $dsn .= isset($spec['persist']) && $spec['persist'] === false ? '' : '?persist';
- return $this->setConnection($dns);
- }else{
- return false;
- }
- }
- /**
- * Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
- *
- * @todo implement freeze correctly for its intended use
- */
- function freeze()
- {
- $this->_freeze = true;
- }
- function isFrozen()
- {
- return !empty($this->_freeze);
- }
- /**
- * Returns true if the given id represents the primary key of a record in the database, false otherwise. Example:
- *
- * $Person->exists(5);
- */
- function exists($id)
- {
- return $this->find('first',array('conditions' => array($this->getPrimaryKey().' = '.$id))) !== false;
- }
- /**
- * Find operates with three different retrieval approaches:
- * * Find by id: This can either be a specific id find(1), a list of ids find(1, 5, 6),
- * or an array of ids find(array(5, 6, 10)). If no record can be found for all of the listed ids,
- * then RecordNotFound will be raised.
- * * Find first: This will return the first record matched by the options used. These options
- * can either be specific conditions or merely an order.
- * If no record can matched, false is returned.
- * * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
- *
- * All approaches accepts an $option array as their last parameter. The options are:
- *
- * 'conditions' => An SQL fragment like "administrator = 1" or array("user_name = ?" => $username). See conditions in the intro.
- * 'order' => An SQL fragment like "created_at DESC, name".
- * 'limit' => An integer determining the limit on the number of rows that should be returned.
- * 'offset' => An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
- * 'joins' => An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = $id". (Rarely needed).
- * 'include' => Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols
- * named refer to already defined associations. See eager loading under Associations.
- *
- * Examples for find by id:
- *
- * $Person->find(1); // returns the object for ID = 1
- * $Person->find(1, 2, 6); // returns an array for objects with IDs in (1, 2, 6), Returns false if any of those IDs is not available
- * $Person->find(array(7, 17)); // returns an array for objects with IDs in (7, 17)
- * $Person->find(array(1)); // returns an array for objects the object with ID = 1
- * $Person->find(1, array('conditions' => "administrator = 1", 'order' => "created_on DESC"));
- *
- * Examples for find first:
- *
- * $Person->find('first'); // returns the first object fetched by SELECT * FROM people
- * $Person->find('first', array('conditions' => array("user_name = ':user_name'", ':user_name' => $user_name)));
- * $Person->find('first', array('order' => "created_on DESC", 'offset' => 5));
- *
- * Examples for find all:
- *
- * $Person->find('all'); // returns an array of objects for all the rows fetched by SELECT * FROM people
- * $Person->find(); // Same as $Person->find('all');
- * $Person->find('all', array('conditions => array("category IN (categories)", 'categories' => join(','$categories)), 'limit' => 50));
- * $Person->find('all', array('offset' => 10, 'limit' => 10));
- * $Person->find('all', array('include' => array('account', 'friends'));
- *
- */
- function &find()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $num_args = func_num_args();
- if($num_args === 2 && func_get_arg(0) == 'set arguments'){
- $args = func_get_arg(1);
- $num_args = count($args);
- }
- $args = $num_args > 0 ? (!isset($args) ? func_get_args() : $args) : array('all');
- if($num_args === 1 && is_numeric($args[0]) && $args[0] > 0){
- $args[0] = (integer)$args[0]; //Cast query by Id
- }
- $options = $num_args > 0 && (is_array($args[$num_args-1]) && isset($args[0][0]) && !is_numeric($args[0][0])) ? array_pop($args) : array();
- //$options = func_get_arg(func_num_args()-1);
- if(!empty($options['conditions']) && is_array($options['conditions'])){
- if (isset($options['conditions'][0]) && strstr($options['conditions'][0], '?') && count($options['conditions']) > 1){
- $pattern = array_shift($options['conditions']);
- $options['bind'] = array_values($options['conditions']);
- $options['conditions'] = $pattern;
- }elseif (isset($options['conditions'][0])){
- $pattern = array_shift($options['conditions']);
- $options['conditions'] = str_replace(array_keys($options['conditions']), array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern);
- }else{
- $options['conditions'] = join(' AND ',(array)$this->getAttributesQuoted($options['conditions']));
- }
- }
- if ($num_args === 2 && !empty($args[0]) && !empty($args[1]) && is_string($args[0]) && ($args[0] == 'all' || $args[0] == 'first') && is_string($args[1])){
- if (!is_array($args[1]) && $args[1] > 0 && $args[0] == 'first'){
- $num_args = 1;
- $args = array($args[1]);
- $options = array();
- }else{
- $options['conditions'] = $args[1];
- $args = array($args[0]);
- }
- }elseif ($num_args === 1 && isset($args[0]) && is_string($args[0]) && $args[0] != 'all' && $args[0] != 'first'){
- $options = array('conditions'=> $args[0]);
- $args = array('first');
- }
- if(!empty($options['conditions']) && is_numeric($options['conditions']) && $options['conditions'] > 0){
- unset($options['conditions']);
- }
- if($num_args > 1){
- if(!empty($args[0]) && is_string($args[0]) && strstr($args[0],'?')){
- $options = array_merge(array('conditions' => array_shift($args)), $options);
- $options['bind'] = $args;
- $args = array('all');
- }elseif (!empty($args[1]) && is_string($args[1]) && strstr($args[1],'?')){
- $_tmp_mode = array_shift($args);
- $options = array_merge(array('conditions' => array_shift($args)),$options);
- $options['bind'] = $args;
- $args = array($_tmp_mode);
- }
- }
- switch ($args[0]) {
- case 'first':
- $options = array_merge($options, array((!empty($options['include']) && $this->hasAssociations() ?'virtual_limit':'limit')=>1));
- $result =& $this->find('all', $options);
- if(!empty($result) && is_array($result)){
- $_result =& $result[0];
- }else{
- $_result = false;
- }
- return $_result;
- break;
- case 'all':
- $limit = isset($options['limit']) ? $options['limit'] : null;
- $offset = isset($options['offset']) ? $options['offset'] : null;
- if((empty($options['conditions']) && empty($options['order']) && is_null($offset) && $this->_getDatabaseType() == 'postgre' ? 1 : 0)){
- $options['order'] = $this->getPrimaryKey();
- }
- $sql = $this->constructFinderSql($options);
- if(!empty($options['bind']) && is_array($options['bind']) && strstr($sql,'?')){
- $sql = array_merge(array($sql),$options['bind']);
- }
- if((!empty($options['include']) && $this->hasAssociations())){
- $result =& $this->findWithAssociations($options, $limit, $offset);
- }else{
- $result =& $this->findBySql($sql, $limit, $offset);
- }
- if(!empty($result) && is_array($result)){
- $_result =& $result;
- }else{
- $_result = false;
- }
- return $_result;
- break;
- default:
- $ids = array_unique(isset($args[0]) ? (is_array($args[0]) ? $args[0] : (array)$args) : array());
- $num_ids = count($ids);
- $num_args = count($args);
- if(isset($ids[$num_ids-1]) && is_array($ids[$num_ids-1])){
- $options = array_merge($options, array_pop($ids));
- $num_ids--;
- }
- if($num_args === 1 && !$args[0] > 0){
- $options['conditions'] = $args[0];
- }
- $conditions = !empty($options['conditions']) ? ' AND '.$options['conditions'] : '';
- if(empty($options) && !empty($args[0]) && !empty($args[1]) && is_array($args[0]) && is_array($args[1])){
- $options = array_pop($args);
- }
- switch ($num_ids){
- case 0 :
- trigger_error($this->t('Couldn\'t find %object_name without an ID%conditions',array('%object_name'=>$this->getModelName(),'%conditions'=>$conditions)), E_USER_ERROR);
- break;
- case 1 :
- $table_name = !empty($options['include']) && $this->hasAssociations() ? '__owner' : $this->getTableName();
- $result =& $this->find('first', array_merge($options, array('conditions' => $table_name.'.'.$this->getPrimaryKey().' = '.$ids[0].$conditions)));
- if(is_array($args[0]) && $result !== false){
- //This is a dirty hack for avoiding PHP4 pass by reference error
- $result_for_ref = array(&$result);
- $_result =& $result_for_ref;
- }else{
- $_result =& $result;
- }
- return $_result;
- break;
- default:
- $ids_condition = $this->getPrimaryKey().' IN ('.join(', ',$ids).')';
- if(!empty($options['conditions']) && is_array($options['conditions'])){
- $options['conditions'][0] = $ids_condition.' AND '.$options['conditions'][0];
- }elseif(!empty($options['conditions'])){
- $options['conditions'] = $ids_condition.' AND '.$options['conditions'];
- }else{
- $without_conditions = true;
- $options['conditions'] = $ids_condition;
- }
- $result =& $this->find('all', $options);
- if(is_array($result) && (count($result) == $num_ids || empty($without_conditions))){
- if($result === false){
- $_result = false;
- }else{
- $_result =& $result;
- }
- return $_result;
- }else{
- $result = false;
- return $result;
- }
- break;
- }
- break;
- }
- $result = false;
- return $result;
- }
- function &findFirst()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('first'),$args));
- return $result;
- }
- function &findAll()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('all'),$args));
- return $result;
- }
- function &objectCache()
- {
- static $cache;
- $args =& func_get_args();
- if(count($args) == 2){
- if(!isset($cache[$args[0]])){
- $cache[$args[0]] =& $args[1];
- }
- }elseif(!isset($cache[$args[0]])){
- return false;
- }
- return $cache[$args[0]];
- }
- /**
- * Gets an array from a string.
- *
- * Acts like Php explode() function but uses any of this as valid separators ' AND ',' and ',' + ',' ',',',';'
- */
- function getArrayFromAkString($string)
- {
- if(is_array($string)){
- return $string;
- }
- $string = str_replace(array(' AND ',' and ',' + ',' ',',',';'),array('|','|','|','','|','|'),trim($string));
- return strstr($string,'|') ? explode('|', $string) : array($string);
- }
- // Gets the column name for use with single table inheritance ? can be overridden in subclasses.
- function getInheritanceColumn()
- {
- return empty($this->_inheritanceColumn) ? ($this->hasColumn('type') ? 'type' : false ) : $this->_inheritanceColumn;
- }
- // Defines the column name for use with single table inheritance ? can be overridden in subclasses.
- function setInheritanceColumn($column_name)
- {
- if(!$this->hasColumn($column_name)){
- trigger_error(Ak::t('Could not set "%column_name" as the inheritance column as this column is not available on the database.',array('%column_name'=>$column_name)), E_USER_NOTICE);
- return false;
- }elseif($this->getColumnType($column_name) != 'string'){
- trigger_error(Ak::t('Could not set %column_name as the inheritance column as this column type is "%column_type" instead of "string".',array('%column_name'=>$column_name,'%column_type'=>$this->getColumnType($column_name))), E_USER_NOTICE);
- return false;
- }else{
- $this->_inheritanceColumn = $column_name;
- return true;
- }
- }
- function getColumnsWithRegexBoundaries()
- {
- $columns = array_keys($this->getColumns());
- foreach ($columns as $k=>$column){
- $columns[$k] = '/([^\.])\b('.$column.')\b/';
- }
- return $columns;
- }
- //SELECT t.code as codigo , c.description as descripcion FROM technical_listings as t LEFT OUTER JOIN categories as c ON c.id = t.category_id
- /**
- * Works like find_all, but requires a complete SQL string. Examples:
- * $Post->findBySql("SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id");
- * $Post->findBySql(array("SELECT * FROM posts WHERE author = ? AND created_on > ?", $author_id, $start_date));
- */
- function &findBySql($sql, $limit = null, $offset = null, $bindings = null)
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- if(is_array($sql)){
- $sql_query = array_shift($sql);
- $bindings = is_array($sql) && count($sql) > 0 ? $sql : array($sql);
- $sql = $sql_query;
- }
- $this->setConnection();
-
- AK_LOG_EVENTS ? $this->_startSqlBlockLog() : null;
-
- $objects = array();
- if(is_integer($limit)){
- if(is_integer($offset)){
- $results = !empty($bindings) ? $this->_db->SelectLimit($sql, $limit, $offset, $bindings) : $this->_db->SelectLimit($sql, $limit, $offset);
- }else {
- $results = !empty($bindings) ? $this->_db->SelectLimit($sql, $limit, -1, $bindings) : $this->_db->SelectLimit($sql, $limit);
- }
- }else{
- $results = !empty($bindings) ? $this->_db->Execute($sql, $bindings) : $this->_db->Execute($sql);
- }
-
- AK_LOG_EVENTS ? $this->_endSqlBlockLog() : null;
- if(!$results){
- AK_DEBUG ? trigger_error($this->_db->ErrorMsg(), E_USER_NOTICE) : null;
- }else{
- $objects = array();
- while ($record = $results->FetchRow()) {
- $objects[] =& $this->instantiate($this->getOnlyAvailableAtrributes($record), false);
- }
- }
- return $objects;
- }
- /**
- * This function pretends to emulate ror finders until AkActiveRecord::addMethod becomes stable on future PHP versions.
- * @todo use PHP5 __call method for handling the magic finder methods like findFirstByUnsenameAndPassword('bermi','pass')
- */
- function &findFirstBy()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- if($args[0] != 'first'){
- array_unshift($args,'first');
- }
- $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
- return $result;
- }
- function &findLastBy()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- $options = array_pop($args);
- if(!is_array($options)){
- array_push($args, $options);
- $options = array();
- }
- $options['order'] = $this->getPrimaryKey().' DESC';
- array_push($args, $options);
- $result =& Ak::call_user_func_array(array(&$this,'findFirstBy'), $args);
- return $result;
- }
- function &findAllBy()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- if($args[0] == 'first'){
- array_shift($args);
- }
- $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
- return $result;
- }
- function &findBy()
- {
- if(!isset($this->_activeRecordHasBeenInstantiated)){
- return Ak::handleStaticCall();
- }
- $args = func_get_args();
- $sql = array_shift($args);
- if($sql == 'all' || $sql == 'first'){
- $fetch = $sql;
- $sql = array_shift($args);
- }else{
- $fetch = 'all';
- }
- $options = array_pop($args);
- if(!is_array($options)){
- array_push($args, $options);
- $options = array();
- }
- $query_values = $args;
- $query_arguments_count = count($query_values);
- $sql = str_replace(array('(',')','||','|','&&','&',' '),array(' ( ',' ) ',' OR ',' OR ',' AND ',' AND ',' '),$sql);
- $operators = array('AND','and','(',')','&','&&','NOT','<>','OR','|','||');
- $pieces = explode(' ',$sql);
- $pieces = array_diff($pieces,array(' ',''));
- $params = array_diff($pieces,$operators);
- $operators = array_diff($pieces,$params);
- $new_sql = '';
- $parameter_count = 0;
- $requested_args = array();
- foreach ($pieces as $piece){
- if(in_array($piece,$params) && $this->hasColumn($piece)){
- $new_sql .= $piece.' = ? ';
- $requested_args[$parameter_count] = $piece;
- $parameter_count++;
- }elseif (!in_array($piece,$operators)){
- if(strstr($piece,':')){
- $_tmp_parts = explode(':',$piece);
- if($this->hasColumn($_tmp_parts[0])){
- switch (strtolower($_tmp_parts[1])) {
- case 'like':
- case '%like%':
- case 'is':
- case 'has':
- case 'contains':
- $query_values[$parameter_count] = '%'.$query_values[$parameter_count].'%';
- $new_sql .= $_tmp_parts[0]." LIKE ? ";
- break;
- case 'like_left':
- case 'like%':
- case 'begins':
- case 'begins_with':
- case 'starts':
- case 'starts_with':
- $query_values[$parameter_count] = $query_values[$parameter_count].'%';
- $new_sql .= $_tmp_parts[0]." LIKEā¦
Large files files are truncated, but you can click here to view the full file