Advertisement
Guest User

parsifal_php-ar_virt-attrs

a guest
Aug 21st, 2011
159
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 51.73 KB | None | 0 0
  1. <?php
  2. /**
  3.  * @package ActiveRecord
  4.  */
  5. namespace ActiveRecord;
  6.  
  7. /**
  8.  * The base class for your models.
  9.  *
  10.  * Defining an ActiveRecord model for a table called people and orders:
  11.  *
  12.  * <code>
  13.  * CREATE TABLE people(
  14.  *   id int primary key auto_increment,
  15.  *   parent_id int,
  16.  *   first_name varchar(50),
  17.  *   last_name varchar(50)
  18.  * );
  19.  *
  20.  * CREATE TABLE orders(
  21.  *   id int primary key auto_increment,
  22.  *   person_id int not null,
  23.  *   cost decimal(10,2),
  24.  *   total decimal(10,2)
  25.  * );
  26.  * </code>
  27.  *
  28.  * <code>
  29.  * class Person extends ActiveRecord\Model {
  30.  *   static $belongs_to = array(
  31.  *     array('parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person')
  32.  *   );
  33.  *
  34.  *   static $has_many = array(
  35.  *     array('children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'),
  36.  *     array('orders')
  37.  *   );
  38.  *
  39.  *   static $validates_length_of = array(
  40.  *     array('first_name', 'within' => array(1,50)),
  41.  *     array('last_name', 'within' => array(1,50))
  42.  *   );
  43.  * }
  44.  *
  45.  * class Order extends ActiveRecord\Model {
  46.  *   static $belongs_to = array(
  47.  *     array('person')
  48.  *   );
  49.  *
  50.  *   static $validates_numericality_of = array(
  51.  *     array('cost', 'greater_than' => 0),
  52.  *     array('total', 'greater_than' => 0)
  53.  *   );
  54.  *
  55.  *   static $before_save = array('calculate_total_with_tax');
  56.  *
  57.  *   public function calculate_total_with_tax() {
  58.  *     $this->total = $this->cost * 0.045;
  59.  *   }
  60.  * }
  61.  * </code>
  62.  *
  63.  * For a more in-depth look at defining models, relationships, callbacks and many other things
  64.  * please consult our {@link http://www.phpactiverecord.org/guides Guides}.
  65.  *
  66.  * @package ActiveRecord
  67.  * @see BelongsTo
  68.  * @see CallBack
  69.  * @see HasMany
  70.  * @see HasAndBelongsToMany
  71.  * @see Serialization
  72.  * @see Validations
  73.  */
  74. class Model
  75. {
  76.     /**
  77.      * An instance of {@link Errors} and will be instantiated once a write method is called.
  78.      *
  79.      * @var Errors
  80.      */
  81.     public $errors;
  82.  
  83.     /**
  84.      * Contains model values as column_name => value
  85.      *
  86.      * @var array
  87.      */
  88.     private $attributes = array();
  89.  
  90.     /**
  91.      * Flag whether or not this model's attributes have been modified since it will either be null or an array of column_names that have been modified
  92.      *
  93.      * @var array
  94.      */
  95.     private $__dirty = null;
  96.  
  97.     /**
  98.      * Flag that determines of this model can have a writer method invoked such as: save/update/insert/delete
  99.      *
  100.      * @var boolean
  101.      */
  102.     private $__readonly = false;
  103.  
  104.     /**
  105.      * Array of relationship objects as model_attribute_name => relationship
  106.      *
  107.      * @var array
  108.      */
  109.     private $__relationships = array();
  110.  
  111.     /**
  112.      * Flag that determines if a call to save() should issue an insert or an update sql statement
  113.      *
  114.      * @var boolean
  115.      */
  116.     private $__new_record = true;
  117.  
  118.     /**
  119.      * Set to the name of the connection this {@link Model} should use.
  120.      *
  121.      * @var string
  122.      */
  123.     static $connection;
  124.  
  125.     /**
  126.      * Set to the name of the database this Model's table is in.
  127.      *
  128.      * @var string
  129.      */
  130.     static $db;
  131.  
  132.     /**
  133.      * Set this to explicitly specify the model's table name if different from inferred name.
  134.      *
  135.      * If your table doesn't follow our table name convention you can set this to the
  136.      * name of your table to explicitly tell ActiveRecord what your table is called.
  137.      *
  138.      * @var string
  139.      */
  140.     static $table_name;
  141.  
  142.     /**
  143.      * Set this to override the default primary key name if different from default name of "id".
  144.      *
  145.      * @var string
  146.      */
  147.     static $primary_key;
  148.  
  149.     /**
  150.      * Set this to explicitly specify the sequence name for the table.
  151.      *
  152.      * @var string
  153.      */
  154.     static $sequence;
  155.  
  156.     /**
  157.      * Allows you to create aliases for attributes.
  158.      *
  159.      * <code>
  160.      * class Person extends ActiveRecord\Model {
  161.      *   static $alias_attribute = array(
  162.      *     'alias_first_name' => 'first_name',
  163.      *     'alias_last_name' => 'last_name');
  164.      * }
  165.      *
  166.      * $person = Person::first();
  167.      * $person->alias_first_name = 'Tito';
  168.      * echo $person->alias_first_name;
  169.      * </code>
  170.      *
  171.      * @var array
  172.      */
  173.     static $alias_attribute = array();
  174.  
  175.     /**
  176.      * Whitelist of attributes that are checked from mass-assignment calls such as constructing a model or using update_attributes.
  177.      *
  178.      * This is the opposite of {@link attr_protected $attr_protected}.
  179.      *
  180.      * <code>
  181.      * class Person extends ActiveRecord\Model {
  182.      *   static $attr_accessible = array('first_name','last_name');
  183.      * }
  184.      *
  185.      * $person = new Person(array(
  186.      *   'first_name' => 'Tito',
  187.      *   'last_name' => 'the Grief',
  188.      *   'id' => 11111));
  189.      *
  190.      * echo $person->id; # => null
  191.      * </code>
  192.      *
  193.      * @var array
  194.      */
  195.     static $attr_accessible = array();
  196.  
  197.     /**
  198.      * Blacklist of attributes that cannot be mass-assigned.
  199.      *
  200.      * This is the opposite of {@link attr_accessible $attr_accessible} and the format
  201.      * for defining these are exactly the same.
  202.      *
  203.      * If the attribute is both accessible and protected, it is treated as protected.
  204.      *
  205.      * @var array
  206.      */
  207.     static $attr_protected = array();
  208.  
  209.     /**
  210.      * Delegates calls to a relationship.
  211.      *
  212.      * <code>
  213.      * class Person extends ActiveRecord\Model {
  214.      *   static $belongs_to = array(array('venue'),array('host'));
  215.      *   static $delegate = array(
  216.      *     array('name', 'state', 'to' => 'venue'),
  217.      *     array('name', 'to' => 'host', 'prefix' => 'woot'));
  218.      * }
  219.      * </code>
  220.      *
  221.      * Can then do:
  222.      *
  223.      * <code>
  224.      * $person->state     # same as calling $person->venue->state
  225.      * $person->name      # same as calling $person->venue->name
  226.      * $person->woot_name # same as calling $person->host->name
  227.      * </code>
  228.      *
  229.      * @var array
  230.      */
  231.     static $delegate = array();
  232.  
  233.     /**
  234.      * Constructs a model.
  235.      *
  236.      * When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)
  237.      * then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given
  238.      * $attributes will be mapped via set_attributes_via_mass_assignment.
  239.      *
  240.      * <code>
  241.      * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
  242.      * </code>
  243.      *
  244.      * @param array $attributes Hash containing names and values to mass assign to the model
  245.      * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes
  246.      * @param boolean $instantiating_via_find Set to true if this model is being created from a find call
  247.      * @param boolean $new_record Set to true if this should be considered a new record
  248.      * @return Model
  249.      */
  250.     public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
  251.     {
  252.         $this->__new_record = $new_record;
  253.  
  254.         // initialize attributes applying defaults
  255.         if (!$instantiating_via_find)
  256.         {
  257.             foreach (static::table()->columns as $name => $meta)
  258.                 $this->attributes[$meta->inflected_name] = $meta->default;
  259.         }
  260.  
  261.         $this->set_attributes_via_mass_assignment($attributes, $guard_attributes);
  262.  
  263.         // since all attribute assignment now goes thru assign_attributes() we want to reset
  264.         // dirty if instantiating via find since nothing is really dirty when doing that
  265.         if ($instantiating_via_find)
  266.             $this->__dirty = array();
  267.  
  268.         // Let the user declare virtual attributes -- accessors that defer to
  269.         // accessor methods.
  270.         // (patch by Sean Gilbertson <sean.gilbertson@gmail.com>)
  271.         if (isset(static::$virtual_attributes)) {
  272.             foreach (static::$virtual_attributes as $attr) {
  273.                 $this->assign_attribute($attr, null);
  274.                 // $this->attributes[$attr] = null;
  275.             }
  276.             $this->reset_dirty();
  277.         }
  278.  
  279.         $this->invoke_callback('after_construct',false);
  280.     }
  281.  
  282.     /**
  283.      * Magic method which delegates to read_attribute(). This handles firing off getter methods,
  284.      * as they are not checked/invoked inside of read_attribute(). This circumvents the problem with
  285.      * a getter being accessed with the same name as an actual attribute.
  286.      *
  287.      * You can also define customer getter methods for the model.
  288.      *
  289.      * EXAMPLE:
  290.      * <code>
  291.      * class User extends ActiveRecord\Model {
  292.      *
  293.      *   # define custom getter methods. Note you must
  294.      *   # prepend get_ to your method name:
  295.      *   function get_middle_initial() {
  296.      *     return $this->middle_name{0};
  297.      *   }
  298.      * }
  299.      *
  300.      * $user = new User();
  301.      * echo $user->middle_name;  # will call $user->get_middle_name()
  302.      * </code>
  303.      *
  304.      * If you define a custom getter with the same name as an attribute then you
  305.      * will need to use read_attribute() to get the attribute's value.
  306.      * This is necessary due to the way __get() works.
  307.      *
  308.      * For example, assume 'name' is a field on the table and we're defining a
  309.      * custom getter for 'name':
  310.      *
  311.      * <code>
  312.      * class User extends ActiveRecord\Model {
  313.      *
  314.      *   # INCORRECT way to do it
  315.      *   # function get_name() {
  316.      *   #   return strtoupper($this->name);
  317.      *   # }
  318.      *
  319.      *   function get_name() {
  320.      *     return strtoupper($this->read_attribute('name'));
  321.      *   }
  322.      * }
  323.      *
  324.      * $user = new User();
  325.      * $user->name = 'bob';
  326.      * echo $user->name; # => BOB
  327.      * </code>
  328.      *
  329.      *
  330.      * @see read_attribute()
  331.      * @param string $name Name of an attribute
  332.      * @return mixed The value of the attribute
  333.      */
  334.     public function &__get($name)
  335.     {
  336.         // check for getter
  337.         if (method_exists($this, "get_$name"))
  338.         {
  339.             $name = "get_$name";
  340.             $value = $this->$name();
  341.             return $value;
  342.         }
  343.  
  344.         return $this->read_attribute($name);
  345.     }
  346.  
  347.     /**
  348.      * Determines if an attribute exists for this {@link Model}.
  349.      *
  350.      * @param string $attribute_name
  351.      * @return boolean
  352.      */
  353.     public function __isset($attribute_name)
  354.     {
  355.         return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);
  356.     }
  357.  
  358.     /**
  359.      * Magic allows un-defined attributes to set via $attributes.
  360.      *
  361.      * You can also define customer setter methods for the model.
  362.      *
  363.      * EXAMPLE:
  364.      * <code>
  365.      * class User extends ActiveRecord\Model {
  366.      *
  367.      *   # define custom setter methods. Note you must
  368.      *   # prepend set_ to your method name:
  369.      *   function set_password($plaintext) {
  370.      *     $this->encrypted_password = md5($plaintext);
  371.      *   }
  372.      * }
  373.      *
  374.      * $user = new User();
  375.      * $user->password = 'plaintext';  # will call $user->set_password('plaintext')
  376.      * </code>
  377.      *
  378.      * If you define a custom setter with the same name as an attribute then you
  379.      * will need to use assign_attribute() to assign the value to the attribute.
  380.      * This is necessary due to the way __set() works.
  381.      *
  382.      * For example, assume 'name' is a field on the table and we're defining a
  383.      * custom setter for 'name':
  384.      *
  385.      * <code>
  386.      * class User extends ActiveRecord\Model {
  387.      *
  388.      *   # INCORRECT way to do it
  389.      *   # function set_name($name) {
  390.      *   #   $this->name = strtoupper($name);
  391.      *   # }
  392.      *
  393.      *   function set_name($name) {
  394.      *     $this->assign_attribute('name',strtoupper($name));
  395.      *   }
  396.      * }
  397.      *
  398.      * $user = new User();
  399.      * $user->name = 'bob';
  400.      * echo $user->name; # => BOB
  401.      * </code>
  402.      *
  403.      * @throws {@link UndefinedPropertyException} if $name does not exist
  404.      * @param string $name Name of attribute, relationship or other to set
  405.      * @param mixed $value The value
  406.      * @return mixed The value
  407.      */
  408.     public function __set($name, $value)
  409.     {
  410.         if (array_key_exists($name, static::$alias_attribute))
  411.             $name = static::$alias_attribute[$name];
  412.  
  413.         elseif (method_exists($this,"set_$name"))
  414.         {
  415.             $name = "set_$name";
  416.             return $this->$name($value);
  417.         }
  418.  
  419.         if (array_key_exists($name,$this->attributes))
  420.             return $this->assign_attribute($name,$value);
  421.  
  422.         if ($name == 'id')
  423.             return $this->assign_attribute($this->get_primary_key(true),$value);
  424.  
  425.         foreach (static::$delegate as &$item)
  426.         {
  427.             if (($delegated_name = $this->is_delegated($name,$item)))
  428.                 return $this->$item['to']->$delegated_name = $value;
  429.         }
  430.  
  431.         throw new UndefinedPropertyException(get_called_class(),$name);
  432.     }
  433.  
  434.     public function __wakeup()
  435.     {
  436.         // make sure the models Table instance gets initialized when waking up
  437.         static::table();
  438.     }
  439.  
  440.     /**
  441.      * Assign a value to an attribute.
  442.      *
  443.      * @param string $name Name of the attribute
  444.      * @param mixed &$value Value of the attribute
  445.      * @return mixed the attribute value
  446.      */
  447.     public function assign_attribute($name, $value)
  448.     {
  449.         $table = static::table();
  450.  
  451.         if (array_key_exists($name,$table->columns) && !is_object($value))
  452.             $value = $table->columns[$name]->cast($value,static::connection());
  453.  
  454.         // convert php's \DateTime to ours
  455.         if ($value instanceof \DateTime)
  456.             $value = new DateTime($value->format('Y-m-d H:i:s T'));
  457.  
  458.         // make sure DateTime values know what model they belong to so
  459.         // dirty stuff works when calling set methods on the DateTime object
  460.         if ($value instanceof DateTime)
  461.             $value->attribute_of($this,$name);
  462.  
  463.         $this->attributes[$name] = $value;
  464.         $this->flag_dirty($name);
  465.         return $value;
  466.     }
  467.  
  468.     /**
  469.      * Retrieves an attribute's value or a relationship object based on the name passed. If the attribute
  470.      * accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is
  471.      * for the primary key.
  472.      *
  473.      * @param string $name Name of an attribute
  474.      * @return mixed The value of the attribute
  475.      * @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...
  476.      */
  477.     public function &read_attribute($name)
  478.     {
  479.         // check for aliased attribute
  480.         if (array_key_exists($name, static::$alias_attribute))
  481.             $name = static::$alias_attribute[$name];
  482.  
  483.         // check for attribute
  484.         if (array_key_exists($name,$this->attributes))
  485.             return $this->attributes[$name];
  486.  
  487.         // check relationships if no attribute
  488.         if (array_key_exists($name,$this->__relationships))
  489.             return $this->__relationships[$name];
  490.  
  491.         $table = static::table();
  492.  
  493.         // this may be first access to the relationship so check Table
  494.         if (($relationship = $table->get_relationship($name)))
  495.         {
  496.             $this->__relationships[$name] = $relationship->load($this);
  497.             return $this->__relationships[$name];
  498.         }
  499.  
  500.         if ($name == 'id')
  501.         {
  502.             $pk = $this->get_primary_key(true);
  503.             if (isset($this->attributes[$pk]))
  504.                 return $this->attributes[$pk];
  505.         }
  506.  
  507.         //do not remove - have to return null by reference in strict mode
  508.         $null = null;
  509.  
  510.         foreach (static::$delegate as &$item)
  511.         {
  512.             if (($delegated_name = $this->is_delegated($name,$item)))
  513.             {
  514.                 $to = $item['to'];
  515.                 if ($this->$to)
  516.                 {
  517.                     $val =& $this->$to->__get($delegated_name);
  518.                     return $val;
  519.                 }
  520.                 else
  521.                     return $null;
  522.             }
  523.         }
  524.  
  525.         throw new UndefinedPropertyException(get_called_class(),$name);
  526.     }
  527.  
  528.     /**
  529.      * Flags an attribute as dirty.
  530.      *
  531.      * @param string $name Attribute name
  532.      */
  533.     public function flag_dirty($name)
  534.     {
  535.         if (!$this->__dirty)
  536.             $this->__dirty = array();
  537.  
  538.         $this->__dirty[$name] = true;
  539.     }
  540.  
  541.     /**
  542.      * Returns hash of attributes that have been modified since loading the model.
  543.      *
  544.      * @return mixed null if no dirty attributes otherwise returns array of dirty attributes.
  545.      */
  546.     public function dirty_attributes()
  547.     {
  548.         if (!$this->__dirty)
  549.             return null;
  550.  
  551.         $dirty = array_intersect_key($this->attributes,$this->__dirty);
  552.         return !empty($dirty) ? $dirty : null;
  553.     }
  554.  
  555.     /**
  556.      * Check if a particular attribute has been modified since loading the model.
  557.      * @param string $attribute Name of the attribute
  558.      * @return boolean TRUE if it has been modified.
  559.      */
  560.     public function attribute_is_dirty($attribute)
  561.     {
  562.         return $this->__dirty && $this->__dirty[$attribute] && array_key_exists($attribute, $this->attributes);
  563.     }
  564.  
  565.     /**
  566.      * Returns a copy of the model's attributes hash.
  567.      *
  568.      * @return array A copy of the model's attribute data
  569.      */
  570.     public function attributes()
  571.     {
  572.         // Make sure to allow for special __get functionality.
  573.         // (patch by Sean Gilbertson <sean.gilbertson@gmail.com>)
  574.         $out = array();
  575.         foreach ($this->attributes as $attr => $val) {
  576.             $out [$attr]= $this->$attr;
  577.         }
  578.        
  579.         return $out;
  580.     }
  581.  
  582.     /**
  583.      * Retrieve the primary key name.
  584.      *
  585.      * @param boolean Set to true to return the first value in the pk array only
  586.      * @return string The primary key for the model
  587.      */
  588.     public function get_primary_key($first=false)
  589.     {
  590.         $pk = static::table()->pk;
  591.         return $first ? $pk[0] : $pk;
  592.     }
  593.  
  594.     /**
  595.      * Returns the actual attribute name if $name is aliased.
  596.      *
  597.      * @param string $name An attribute name
  598.      * @return string
  599.      */
  600.     public function get_real_attribute_name($name)
  601.     {
  602.         if (array_key_exists($name,$this->attributes))
  603.             return $name;
  604.  
  605.         if (array_key_exists($name,static::$alias_attribute))
  606.             return static::$alias_attribute[$name];
  607.  
  608.         return null;
  609.     }
  610.  
  611.     /**
  612.      * Returns array of validator data for this Model.
  613.      *
  614.      * Will return an array looking like:
  615.      *
  616.      * <code>
  617.      * array(
  618.      *   'name' => array(
  619.      *     array('validator' => 'validates_presence_of'),
  620.      *     array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),
  621.      *   'password' => array(
  622.      *     array('validator' => 'validates_length_of', 'minimum' => 6))
  623.      *   )
  624.      * );
  625.      * </code>
  626.      *
  627.      * @return array An array containing validator data for this model.
  628.      */
  629.     public function get_validation_rules()
  630.     {
  631.         require_once 'Validations.php';
  632.  
  633.         $validator = new Validations($this);
  634.         return $validator->rules();
  635.     }
  636.  
  637.     /**
  638.      * Returns an associative array containing values for all the attributes in $attributes
  639.      *
  640.      * @param array $attributes Array containing attribute names
  641.      * @return array A hash containing $name => $value
  642.      */
  643.     public function get_values_for($attributes)
  644.     {
  645.         $ret = array();
  646.  
  647.         foreach ($attributes as $name)
  648.         {
  649.             if (array_key_exists($name,$this->attributes))
  650.                 $ret[$name] = $this->attributes[$name];
  651.         }
  652.         return $ret;
  653.     }
  654.  
  655.     /**
  656.      * Retrieves the name of the table for this Model.
  657.      *
  658.      * @return string
  659.      */
  660.     public static function table_name()
  661.     {
  662.         return static::table()->table;
  663.     }
  664.  
  665.     /**
  666.      * Returns the attribute name on the delegated relationship if $name is
  667.      * delegated or null if not delegated.
  668.      *
  669.      * @param string $name Name of an attribute
  670.      * @param array $delegate An array containing delegate data
  671.      * @return delegated attribute name or null
  672.      */
  673.     private function is_delegated($name, &$delegate)
  674.     {
  675.         if ($delegate['prefix'] != '')
  676.             $name = substr($name,strlen($delegate['prefix'])+1);
  677.  
  678.         if (is_array($delegate) && in_array($name,$delegate['delegate']))
  679.             return $name;
  680.  
  681.         return null;
  682.     }
  683.  
  684.     /**
  685.      * Determine if the model is in read-only mode.
  686.      *
  687.      * @return boolean
  688.      */
  689.     public function is_readonly()
  690.     {
  691.         return $this->__readonly;
  692.     }
  693.  
  694.     /**
  695.      * Determine if the model is a new record.
  696.      *
  697.      * @return boolean
  698.      */
  699.     public function is_new_record()
  700.     {
  701.         return $this->__new_record;
  702.     }
  703.  
  704.     /**
  705.      * Throws an exception if this model is set to readonly.
  706.      *
  707.      * @throws ActiveRecord\ReadOnlyException
  708.      * @param string $method_name Name of method that was invoked on model for exception message
  709.      */
  710.     private function verify_not_readonly($method_name)
  711.     {
  712.         if ($this->is_readonly())
  713.             throw new ReadOnlyException(get_class($this), $method_name);
  714.     }
  715.  
  716.     /**
  717.      * Flag model as readonly.
  718.      *
  719.      * @param boolean $readonly Set to true to put the model into readonly mode
  720.      */
  721.     public function readonly($readonly=true)
  722.     {
  723.         $this->__readonly = $readonly;
  724.     }
  725.  
  726.     /**
  727.      * Retrieve the connection for this model.
  728.      *
  729.      * @return Connection
  730.      */
  731.     public static function connection()
  732.     {
  733.         return static::table()->conn;
  734.     }
  735.  
  736.     /**
  737.      * Re-establishes the database connection with a new connection.
  738.      *
  739.      * @return Connection
  740.      */
  741.     public static function reestablish_connection()
  742.     {
  743.         return static::table()->reestablish_connection();
  744.     }
  745.  
  746.     /**
  747.      * Returns the {@link Table} object for this model.
  748.      *
  749.      * Be sure to call in static scoping: static::table()
  750.      *
  751.      * @return Table
  752.      */
  753.     public static function table()
  754.     {
  755.         return Table::load(get_called_class());
  756.     }
  757.  
  758.     /**
  759.      * Creates a model and saves it to the database.
  760.      *
  761.      * @param array $attributes Array of the models attributes
  762.      * @param boolean $validate True if the validators should be run
  763.      * @return Model
  764.      */
  765.     public static function create($attributes, $validate=true)
  766.     {
  767.         $class_name = get_called_class();
  768.         $model = new $class_name($attributes);
  769.         $model->save($validate);
  770.         return $model;
  771.     }
  772.  
  773.     /**
  774.      * Save the model to the database.
  775.      *
  776.      * This function will automatically determine if an INSERT or UPDATE needs to occur.
  777.      * If a validation or a callback for this model returns false, then the model will
  778.      * not be saved and this will return false.
  779.      *
  780.      * If saving an existing model only data that has changed will be saved.
  781.      *
  782.      * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  783.      * @return boolean True if the model was saved to the database otherwise false
  784.      */
  785.     public function save($validate=true)
  786.     {
  787.         $this->verify_not_readonly('save');
  788.         return $this->is_new_record() ? $this->insert($validate) : $this->update($validate);
  789.     }
  790.  
  791.     /**
  792.      * Issue an INSERT sql statement for this model's attribute.
  793.      *
  794.      * @see save
  795.      * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  796.      * @return boolean True if the model was saved to the database otherwise false
  797.      */
  798.     private function insert($validate=true)
  799.     {
  800.         $this->verify_not_readonly('insert');
  801.  
  802.         if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
  803.             return false;
  804.  
  805.         $table = static::table();
  806.  
  807.         if (!($attributes = $this->dirty_attributes()))
  808.             $attributes = $this->attributes;
  809.  
  810.         $pk = $this->get_primary_key(true);
  811.         $use_sequence = false;
  812.  
  813.         if ($table->sequence && !isset($attributes[$pk]))
  814.         {
  815.             if (($conn = static::connection()) instanceof OciAdapter)
  816.             {
  817.                 // terrible oracle makes us select the nextval first
  818.                 $attributes[$pk] = $conn->get_next_sequence_value($table->sequence);
  819.                 $table->insert($attributes);
  820.                 $this->attributes[$pk] = $attributes[$pk];
  821.             }
  822.             else
  823.             {
  824.                 // unset pk that was set to null
  825.                 if (array_key_exists($pk,$attributes))
  826.                     unset($attributes[$pk]);
  827.  
  828.                 $table->insert($attributes,$pk,$table->sequence);
  829.                 $use_sequence = true;
  830.             }
  831.         }
  832.         else
  833.             $table->insert($attributes);
  834.  
  835.         // if we've got an autoincrementing/sequenced pk set it
  836.         // don't need this check until the day comes that we decide to support composite pks
  837.         // if (count($pk) == 1)
  838.         {
  839.             $column = $table->get_column_by_inflected_name($pk);
  840.  
  841.             if ($column->auto_increment || $use_sequence)
  842.                 $this->attributes[$pk] = static::connection()->insert_id($table->sequence);
  843.         }
  844.  
  845.         $this->invoke_callback('after_create',false);
  846.         $this->__new_record = false;
  847.         return true;
  848.     }
  849.  
  850.     /**
  851.      * Issue an UPDATE sql statement for this model's dirty attributes.
  852.      *
  853.      * @see save
  854.      * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  855.      * @return boolean True if the model was saved to the database otherwise false
  856.      */
  857.     private function update($validate=true)
  858.     {
  859.         $this->verify_not_readonly('update');
  860.  
  861.         if ($validate && !$this->_validate())
  862.             return false;
  863.  
  864.         if ($this->is_dirty())
  865.         {
  866.             $pk = $this->values_for_pk();
  867.  
  868.             if (empty($pk))
  869.                 throw new ActiveRecordException("Cannot update, no primary key defined for: " . get_called_class());
  870.  
  871.             if (!$this->invoke_callback('before_update',false))
  872.                 return false;
  873.  
  874.             $dirty = $this->dirty_attributes();
  875.             static::table()->update($dirty,$pk);
  876.             $this->invoke_callback('after_update',false);
  877.         }
  878.  
  879.         return true;
  880.     }
  881.  
  882.     /**
  883.      * Deletes records matching conditions in $options
  884.      *
  885.      * Does not instantiate models and therefore does not invoke callbacks
  886.      *
  887.      * Delete all using a hash:
  888.      *
  889.      * <code>
  890.      * YourModel::delete_all(array('conditions' => array('name' => 'Tito')));
  891.      * </code>
  892.      *
  893.      * Delete all using an array:
  894.      *
  895.      * <code>
  896.      * YourModel::delete_all(array('conditions' => array('name = ?', 'Tito')));
  897.      * </code>
  898.      *
  899.      * Delete all using a string:
  900.      *
  901.      * <code>
  902.      * YourModel::delete_all(array('conditions' => 'name = "Tito"));
  903.      * </code>
  904.      *
  905.      * An options array takes the following parameters:
  906.      *
  907.      * <ul>
  908.      * <li><b>conditions:</b> Conditions using a string/hash/array</li>
  909.      * <li><b>limit:</b> Limit number of records to delete (MySQL & Sqlite only)</li>
  910.      * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
  911.      * </ul>
  912.      *
  913.      * @params array $options
  914.      * return integer Number of rows affected
  915.      */
  916.     public static function delete_all($options=array())
  917.     {
  918.         $table = static::table();
  919.         $conn = static::connection();
  920.         $sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
  921.  
  922.         $conditions = is_array($options) ? $options['conditions'] : $options;
  923.  
  924.         if (is_array($conditions) && !is_hash($conditions))
  925.             call_user_func_array(array($sql, 'delete'), $conditions);
  926.         else
  927.             $sql->delete($conditions);
  928.  
  929.         if (isset($options['limit']))
  930.             $sql->limit($options['limit']);
  931.  
  932.         if (isset($options['order']))
  933.             $sql->order($options['order']);
  934.  
  935.         $values = $sql->bind_values();
  936.         $ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
  937.         return $ret->rowCount();
  938.     }
  939.  
  940.     /**
  941.      * Updates records using set in $options
  942.      *
  943.      * Does not instantiate models and therefore does not invoke callbacks
  944.      *
  945.      * Update all using a hash:
  946.      *
  947.      * <code>
  948.      * YourModel::update_all(array('set' => array('name' => "Bob")));
  949.      * </code>
  950.      *
  951.      * Update all using a string:
  952.      *
  953.      * <code>
  954.      * YourModel::update_all(array('set' => 'name = "Bob"'));
  955.      * </code>
  956.      *
  957.      * An options array takes the following parameters:
  958.      *
  959.      * <ul>
  960.      * <li><b>set:</b> String/hash of field names and their values to be updated with
  961.      * <li><b>conditions:</b> Conditions using a string/hash/array</li>
  962.      * <li><b>limit:</b> Limit number of records to update (MySQL & Sqlite only)</li>
  963.      * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
  964.      * </ul>
  965.      *
  966.      * @params array $options
  967.      * return integer Number of rows affected
  968.      */
  969.     public static function update_all($options=array())
  970.     {
  971.         $table = static::table();
  972.         $conn = static::connection();
  973.         $sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
  974.  
  975.         $sql->update($options['set']);
  976.  
  977.         if (isset($options['conditions']) && ($conditions = $options['conditions']))
  978.         {
  979.             if (is_array($conditions) && !is_hash($conditions))
  980.                 call_user_func_array(array($sql, 'where'), $conditions);
  981.             else
  982.                 $sql->where($conditions);
  983.         }
  984.  
  985.         if (isset($options['limit']))
  986.             $sql->limit($options['limit']);
  987.  
  988.         if (isset($options['order']))
  989.             $sql->order($options['order']);
  990.  
  991.         $values = $sql->bind_values();
  992.         $ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
  993.         return $ret->rowCount();
  994.  
  995.     }
  996.  
  997.     /**
  998.      * Deletes this model from the database and returns true if successful.
  999.      *
  1000.      * @return boolean
  1001.      */
  1002.     public function delete()
  1003.     {
  1004.         $this->verify_not_readonly('delete');
  1005.  
  1006.         $pk = $this->values_for_pk();
  1007.  
  1008.         if (empty($pk))
  1009.             throw new ActiveRecordException("Cannot delete, no primary key defined for: " . get_called_class());
  1010.  
  1011.         if (!$this->invoke_callback('before_destroy',false))
  1012.             return false;
  1013.  
  1014.         static::table()->delete($pk);
  1015.         $this->invoke_callback('after_destroy',false);
  1016.  
  1017.         return true;
  1018.     }
  1019.  
  1020.     /**
  1021.      * Helper that creates an array of values for the primary key(s).
  1022.      *
  1023.      * @return array An array in the form array(key_name => value, ...)
  1024.      */
  1025.     public function values_for_pk()
  1026.     {
  1027.         return $this->values_for(static::table()->pk);
  1028.     }
  1029.  
  1030.     /**
  1031.      * Helper to return a hash of values for the specified attributes.
  1032.      *
  1033.      * @param array $attribute_names Array of attribute names
  1034.      * @return array An array in the form array(name => value, ...)
  1035.      */
  1036.     public function values_for($attribute_names)
  1037.     {
  1038.         $filter = array();
  1039.  
  1040.         foreach ($attribute_names as $name)
  1041.             $filter[$name] = $this->$name;
  1042.  
  1043.         return $filter;
  1044.     }
  1045.  
  1046.     /**
  1047.      * Validates the model.
  1048.      *
  1049.      * @return boolean True if passed validators otherwise false
  1050.      */
  1051.     private function _validate()
  1052.     {
  1053.         require_once 'Validations.php';
  1054.  
  1055.         $validator = new Validations($this);
  1056.         $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
  1057.  
  1058.         foreach (array('before_validation', "before_$validation_on") as $callback)
  1059.         {
  1060.             if (!$this->invoke_callback($callback,false))
  1061.                 return false;
  1062.         }
  1063.  
  1064.         // need to store reference b4 validating so that custom validators have access to add errors
  1065.         $this->errors = $validator->get_record();
  1066.         $validator->validate();
  1067.  
  1068.         foreach (array('after_validation', "after_$validation_on") as $callback)
  1069.             $this->invoke_callback($callback,false);
  1070.  
  1071.         if (!$this->errors->is_empty())
  1072.             return false;
  1073.  
  1074.         return true;
  1075.     }
  1076.  
  1077.     /**
  1078.      * Returns true if the model has been modified.
  1079.      *
  1080.      * @return boolean true if modified
  1081.      */
  1082.     public function is_dirty()
  1083.     {
  1084.         return empty($this->__dirty) ? false : true;
  1085.     }
  1086.  
  1087.     /**
  1088.      * Run validations on model and returns whether or not model passed validation.
  1089.      *
  1090.      * @see is_invalid
  1091.      * @return boolean
  1092.      */
  1093.     public function is_valid()
  1094.     {
  1095.         return $this->_validate();
  1096.     }
  1097.  
  1098.     /**
  1099.      * Runs validations and returns true if invalid.
  1100.      *
  1101.      * @see is_valid
  1102.      * @return boolean
  1103.      */
  1104.     public function is_invalid()
  1105.     {
  1106.         return !$this->_validate();
  1107.     }
  1108.  
  1109.     /**
  1110.      * Updates a model's timestamps.
  1111.      */
  1112.     public function set_timestamps()
  1113.     {
  1114.         $now = date('Y-m-d H:i:s');
  1115.  
  1116.         if (isset($this->updated_at))
  1117.             $this->updated_at = $now;
  1118.  
  1119.         if (isset($this->created_at) && $this->is_new_record())
  1120.             $this->created_at = $now;
  1121.     }
  1122.  
  1123.     /**
  1124.      * Mass update the model with an array of attribute data and saves to the database.
  1125.      *
  1126.      * @param array $attributes An attribute data array in the form array(name => value, ...)
  1127.      * @return boolean True if successfully updated and saved otherwise false
  1128.      */
  1129.     public function update_attributes($attributes)
  1130.     {
  1131.         $this->set_attributes($attributes);
  1132.         return $this->save();
  1133.     }
  1134.  
  1135.     /**
  1136.      * Updates a single attribute and saves the record without going through the normal validation procedure.
  1137.      *
  1138.      * @param string $name Name of attribute
  1139.      * @param mixed $value Value of the attribute
  1140.      * @return boolean True if successful otherwise false
  1141.      */
  1142.     public function update_attribute($name, $value)
  1143.     {
  1144.         $this->__set($name, $value);
  1145.         return $this->update(false);
  1146.     }
  1147.  
  1148.     /**
  1149.      * Mass update the model with data from an attributes hash.
  1150.      *
  1151.      * Unlike update_attributes() this method only updates the model's data
  1152.      * but DOES NOT save it to the database.
  1153.      *
  1154.      * @see update_attributes
  1155.      * @param array $attributes An array containing data to update in the form array(name => value, ...)
  1156.      */
  1157.     public function set_attributes(array $attributes)
  1158.     {
  1159.         $this->set_attributes_via_mass_assignment($attributes, true);
  1160.     }
  1161.  
  1162.     /**
  1163.      * Passing $guard_attributes as true will throw an exception if an attribute does not exist.
  1164.      *
  1165.      * @throws ActiveRecord\UndefinedPropertyException
  1166.      * @param array $attributes An array in the form array(name => value, ...)
  1167.      * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded
  1168.      */
  1169.     private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
  1170.     {
  1171.         //access uninflected columns since that is what we would have in result set
  1172.         $table = static::table();
  1173.         $exceptions = array();
  1174.         $use_attr_accessible = !empty(static::$attr_accessible);
  1175.         $use_attr_protected = !empty(static::$attr_protected);
  1176.         $connection = static::connection();
  1177.  
  1178.         foreach ($attributes as $name => $value)
  1179.         {
  1180.             // is a normal field on the table
  1181.             if (array_key_exists($name,$table->columns))
  1182.             {
  1183.                 $value = $table->columns[$name]->cast($value,$connection);
  1184.                 $name = $table->columns[$name]->inflected_name;
  1185.             }
  1186.  
  1187.             if ($guard_attributes)
  1188.             {
  1189.                 if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
  1190.                     continue;
  1191.  
  1192.                 if ($use_attr_protected && in_array($name,static::$attr_protected))
  1193.                     continue;
  1194.  
  1195.                 // set valid table data
  1196.                 try {
  1197.                     $this->$name = $value;
  1198.                 } catch (UndefinedPropertyException $e) {
  1199.                     $exceptions[] = $e->getMessage();
  1200.                 }
  1201.             }
  1202.             else
  1203.             {
  1204.                 // ignore OciAdapter's limit() stuff
  1205.                 if ($name == 'ar_rnum__')
  1206.                     continue;
  1207.  
  1208.                 // set arbitrary data
  1209.                 $this->assign_attribute($name,$value);
  1210.             }
  1211.         }
  1212.  
  1213.         if (!empty($exceptions))
  1214.             throw new UndefinedPropertyException(get_called_class(),$exceptions);
  1215.     }
  1216.  
  1217.     /**
  1218.      * Add a model to the given named ($name) relationship.
  1219.      *
  1220.      * @internal This should <strong>only</strong> be used by eager load
  1221.      * @param Model $model
  1222.      * @param $name of relationship for this table
  1223.      * @return void
  1224.      */
  1225.     public function set_relationship_from_eager_load(Model $model=null, $name)
  1226.     {
  1227.         $table = static::table();
  1228.  
  1229.         if (($rel = $table->get_relationship($name)))
  1230.         {
  1231.             if ($rel->is_poly())
  1232.             {
  1233.                 // if the related model is null and it is a poly then we should have an empty array
  1234.                 if (is_null($model))
  1235.                     return $this->__relationships[$name] = array();
  1236.                 else
  1237.                     return $this->__relationships[$name][] = $model;
  1238.             }
  1239.             else
  1240.                 return $this->__relationships[$name] = $model;
  1241.         }
  1242.  
  1243.         throw new RelationshipException("Relationship named $name has not been declared for class: {$table->class->getName()}");
  1244.     }
  1245.  
  1246.     /**
  1247.      * Reloads the attributes and relationships of this object from the database.
  1248.      *
  1249.      * @return Model
  1250.      */
  1251.     public function reload()
  1252.     {
  1253.         $this->__relationships = array();
  1254.         $pk = array_values($this->get_values_for($this->get_primary_key()));
  1255.  
  1256.         $this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false);
  1257.         $this->reset_dirty();
  1258.  
  1259.         return $this;
  1260.     }
  1261.  
  1262.     public function __clone()
  1263.     {
  1264.         $this->__relationships = array();
  1265.         $this->reset_dirty();
  1266.         return $this;
  1267.     }
  1268.  
  1269.     /**
  1270.      * Resets the dirty array.
  1271.      *
  1272.      * @see dirty_attributes
  1273.      */
  1274.     public function reset_dirty()
  1275.     {
  1276.         $this->__dirty = null;
  1277.     }
  1278.  
  1279.     /**
  1280.      * A list of valid finder options.
  1281.      *
  1282.      * @var array
  1283.      */
  1284.     static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
  1285.  
  1286.     /**
  1287.      * Enables the use of dynamic finders.
  1288.      *
  1289.      * Dynamic finders are just an easy way to do queries quickly without having to
  1290.      * specify an options array with conditions in it.
  1291.      *
  1292.      * <code>
  1293.      * SomeModel::find_by_first_name('Tito');
  1294.      * SomeModel::find_by_first_name_and_last_name('Tito','the Grief');
  1295.      * SomeModel::find_by_first_name_or_last_name('Tito','the Grief');
  1296.      * SomeModel::find_all_by_last_name('Smith');
  1297.      * SomeModel::count_by_name('Bob')
  1298.      * SomeModel::count_by_name_or_state('Bob','VA')
  1299.      * SomeModel::count_by_name_and_state('Bob','VA')
  1300.      * </code>
  1301.      *
  1302.      * You can also create the model if the find call returned no results:
  1303.      *
  1304.      * <code>
  1305.      * Person::find_or_create_by_name('Tito');
  1306.      *
  1307.      * # would be the equivalent of
  1308.      * if (!Person::find_by_name('Tito'))
  1309.      *   Person::create(array('Tito'));
  1310.      * </code>
  1311.      *
  1312.      * Some other examples of find_or_create_by:
  1313.      *
  1314.      * <code>
  1315.      * Person::find_or_create_by_name_and_id('Tito',1);
  1316.      * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));
  1317.      * </code>
  1318.      *
  1319.      * @param string $method Name of method
  1320.      * @param mixed $args Method args
  1321.      * @return Model
  1322.      * @throws {@link ActiveRecordException} if invalid query
  1323.      * @see find
  1324.      */
  1325.     public static function __callStatic($method, $args)
  1326.     {
  1327.         $options = static::extract_and_validate_options($args);
  1328.         $create = false;
  1329.  
  1330.         if (substr($method,0,17) == 'find_or_create_by')
  1331.         {
  1332.             $attributes = substr($method,17);
  1333.  
  1334.             // can't take any finders with OR in it when doing a find_or_create_by
  1335.             if (strpos($attributes,'_or_') !== false)
  1336.                 throw new ActiveRecordException("Cannot use OR'd attributes in find_or_create_by");
  1337.  
  1338.             $create = true;
  1339.             $method = 'find_by' . substr($method,17);
  1340.         }
  1341.  
  1342.         if (substr($method,0,7) === 'find_by')
  1343.         {
  1344.             $attributes = substr($method,8);
  1345.             $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),$attributes,$args,static::$alias_attribute);
  1346.  
  1347.             if (!($ret = static::find('first',$options)) && $create)
  1348.                 return static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));
  1349.  
  1350.             return $ret;
  1351.         }
  1352.         elseif (substr($method,0,11) === 'find_all_by')
  1353.         {
  1354.             $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,12),$args,static::$alias_attribute);
  1355.             return static::find('all',$options);
  1356.         }
  1357.         elseif (substr($method,0,8) === 'count_by')
  1358.         {
  1359.             $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,9),$args,static::$alias_attribute);
  1360.             return static::count($options);
  1361.         }
  1362.  
  1363.         throw new ActiveRecordException("Call to undefined method: $method");
  1364.     }
  1365.  
  1366.     /**
  1367.      * Enables the use of build|create for associations.
  1368.      *
  1369.      * @param string $method Name of method
  1370.      * @param mixed $args Method args
  1371.      * @return mixed An instance of a given {@link AbstractRelationship}
  1372.      */
  1373.     public function __call($method, $args)
  1374.     {
  1375.         //check for build|create_association methods
  1376.         if (preg_match('/(build|create)_/', $method))
  1377.         {
  1378.             if (!empty($args))
  1379.                 $args = $args[0];
  1380.  
  1381.             $association_name = str_replace(array('build_', 'create_'), '', $method);
  1382.             $method = str_replace($association_name, 'association', $method);
  1383.             $table = static::table();
  1384.  
  1385.             if (($association = $table->get_relationship($association_name)) ||
  1386.                   ($association = $table->get_relationship(($association_name = Utils::pluralize($association_name)))))
  1387.             {
  1388.                 // access association to ensure that the relationship has been loaded
  1389.                 // so that we do not double-up on records if we append a newly created
  1390.                 $this->$association_name;
  1391.                 return $association->$method($this, $args);
  1392.             }
  1393.         }
  1394.  
  1395.         throw new ActiveRecordException("Call to undefined method: $method");
  1396.     }
  1397.  
  1398.     /**
  1399.      * Alias for self::find('all').
  1400.      *
  1401.      * @see find
  1402.      * @return array array of records found
  1403.      */
  1404.     public static function all(/* ... */)
  1405.     {
  1406.         return call_user_func_array('static::find',array_merge(array('all'),func_get_args()));
  1407.     }
  1408.  
  1409.     /**
  1410.      * Get a count of qualifying records.
  1411.      *
  1412.      * <code>
  1413.      * YourModel::count(array('conditions' => 'amount > 3.14159265'));
  1414.      * </code>
  1415.      *
  1416.      * @see find
  1417.      * @return int Number of records that matched the query
  1418.      */
  1419.     public static function count(/* ... */)
  1420.     {
  1421.         $args = func_get_args();
  1422.         $options = static::extract_and_validate_options($args);
  1423.         $options['select'] = 'COUNT(*)';
  1424.  
  1425.         if (!empty($args))
  1426.         {
  1427.             if (is_hash($args[0]))
  1428.                 $options['conditions'] = $args[0];
  1429.             else
  1430.                 $options['conditions'] = call_user_func_array('static::pk_conditions',$args);
  1431.         }
  1432.  
  1433.         $table = static::table();
  1434.         $sql = $table->options_to_sql($options);
  1435.         $values = $sql->get_where_values();
  1436.         return static::connection()->query_and_fetch_one($sql->to_s(),$values);
  1437.     }
  1438.  
  1439.     /**
  1440.      * Determine if a record exists.
  1441.      *
  1442.      * <code>
  1443.      * SomeModel::exists(123);
  1444.      * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));
  1445.      * SomeModel::exists(array('id' => 123, 'name' => 'Tito'));
  1446.      * </code>
  1447.      *
  1448.      * @see find
  1449.      * @return boolean
  1450.      */
  1451.     public static function exists(/* ... */)
  1452.     {
  1453.         return call_user_func_array('static::count',func_get_args()) > 0 ? true : false;
  1454.     }
  1455.  
  1456.     /**
  1457.      * Alias for self::find('first').
  1458.      *
  1459.      * @see find
  1460.      * @return Model The first matched record or null if not found
  1461.      */
  1462.     public static function first(/* ... */)
  1463.     {
  1464.         return call_user_func_array('static::find',array_merge(array('first'),func_get_args()));
  1465.     }
  1466.  
  1467.     /**
  1468.      * Alias for self::find('last')
  1469.      *
  1470.      * @see find
  1471.      * @return Model The last matched record or null if not found
  1472.      */
  1473.     public static function last(/* ... */)
  1474.     {
  1475.         return call_user_func_array('static::find',array_merge(array('last'),func_get_args()));
  1476.     }
  1477.  
  1478.     /**
  1479.      * Find records in the database.
  1480.      *
  1481.      * Finding by the primary key:
  1482.      *
  1483.      * <code>
  1484.      * # queries for the model with id=123
  1485.      * YourModel::find(123);
  1486.      *
  1487.      * # queries for model with id in(1,2,3)
  1488.      * YourModel::find(1,2,3);
  1489.      *
  1490.      * # finding by pk accepts an options array
  1491.      * YourModel::find(123,array('order' => 'name desc'));
  1492.      * </code>
  1493.      *
  1494.      * Finding by using a conditions array:
  1495.      *
  1496.      * <code>
  1497.      * YourModel::find('first', array('conditions' => array('name=?','Tito'),
  1498.      *   'order' => 'name asc'))
  1499.      * YourModel::find('all', array('conditions' => 'amount > 3.14159265'));
  1500.      * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));
  1501.      * </code>
  1502.      *
  1503.      * Finding by using a hash:
  1504.      *
  1505.      * <code>
  1506.      * YourModel::find(array('name' => 'Tito', 'id' => 1));
  1507.      * YourModel::find('first',array('name' => 'Tito', 'id' => 1));
  1508.      * YourModel::find('all',array('name' => 'Tito', 'id' => 1));
  1509.      * </code>
  1510.      *
  1511.      * An options array can take the following parameters:
  1512.      *
  1513.      * <ul>
  1514.      * <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>
  1515.      * <li><b>joins:</b> A SQL join fragment such as: 'JOIN roles ON(roles.user_id=user.id)' or a named association on the model</li>
  1516.      * <li><b>include:</b> TODO not implemented yet</li>
  1517.      * <li><b>conditions:</b> A SQL fragment such as: 'id=1', array('id=1'), array('name=? and id=?','Tito',1), array('name IN(?)', array('Tito','Bob')),
  1518.      * array('name' => 'Tito', 'id' => 1)</li>
  1519.      * <li><b>limit:</b> Number of records to limit the query to</li>
  1520.      * <li><b>offset:</b> The row offset to return results from for the query</li>
  1521.      * <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>
  1522.      * <li><b>readonly:</b> Return all the models in readonly mode</li>
  1523.      * <li><b>group:</b> A SQL group by fragment</li>
  1524.      * </ul>
  1525.      *
  1526.      * @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched
  1527.      * @return mixed An array of records found if doing a find_all otherwise a
  1528.      *   single Model object or null if it wasn't found. NULL is only return when
  1529.      *   doing a first/last find. If doing an all find and no records matched this
  1530.      *   will return an empty array.
  1531.      */
  1532.     public static function find(/* $type, $options */)
  1533.     {
  1534.         $class = get_called_class();
  1535.  
  1536.         if (func_num_args() <= 0)
  1537.             throw new RecordNotFound("Couldn't find $class without an ID");
  1538.  
  1539.         $args = func_get_args();
  1540.         $options = static::extract_and_validate_options($args);
  1541.         $num_args = count($args);
  1542.         $single = true;
  1543.  
  1544.         if ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))
  1545.         {
  1546.             switch ($args[0])
  1547.             {
  1548.                 case 'all':
  1549.                     $single = false;
  1550.                     break;
  1551.  
  1552.                 case 'last':
  1553.                     if (!array_key_exists('order',$options))
  1554.                         $options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';
  1555.                     else
  1556.                         $options['order'] = SQLBuilder::reverse_order($options['order']);
  1557.  
  1558.                     // fall thru
  1559.  
  1560.                 case 'first':
  1561.                     $options['limit'] = 1;
  1562.                     $options['offset'] = 0;
  1563.                     break;
  1564.             }
  1565.  
  1566.             $args = array_slice($args,1);
  1567.             $num_args--;
  1568.         }
  1569.         //find by pk
  1570.         elseif (1 === count($args) && 1 == $num_args)
  1571.             $args = $args[0];
  1572.  
  1573.         // anything left in $args is a find by pk
  1574.         if ($num_args > 0 && !isset($options['conditions']))
  1575.             return static::find_by_pk($args, $options);
  1576.  
  1577.         $options['mapped_names'] = static::$alias_attribute;
  1578.         $list = static::table()->find($options);
  1579.  
  1580.         return $single ? (!empty($list) ? $list[0] : null) : $list;
  1581.     }
  1582.  
  1583.     /**
  1584.      * Finder method which will find by a single or array of primary keys for this model.
  1585.      *
  1586.      * @see find
  1587.      * @param array $values An array containing values for the pk
  1588.      * @param array $options An options array
  1589.      * @return Model
  1590.      * @throws {@link RecordNotFound} if a record could not be found
  1591.      */
  1592.     public static function find_by_pk($values, $options)
  1593.     {
  1594.         $options['conditions'] = static::pk_conditions($values);
  1595.         $list = static::table()->find($options);
  1596.         $results = count($list);
  1597.  
  1598.         if ($results != ($expected = count($values)))
  1599.         {
  1600.             $class = get_called_class();
  1601.  
  1602.             if ($expected == 1)
  1603.             {
  1604.                 if (!is_array($values))
  1605.                     $values = array($values);
  1606.  
  1607.                 throw new RecordNotFound("Couldn't find $class with ID=" . join(',',$values));
  1608.             }
  1609.  
  1610.             $values = join(',',$values);
  1611.             throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
  1612.         }
  1613.         return $expected == 1 ? $list[0] : $list;
  1614.     }
  1615.  
  1616.     /**
  1617.      * Find using a raw SELECT query.
  1618.      *
  1619.      * <code>
  1620.      * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito'));
  1621.      * YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'");
  1622.      * </code>
  1623.      *
  1624.      * @param string $sql The raw SELECT query
  1625.      * @param array $values An array of values for any parameters that needs to be bound
  1626.      * @return array An array of models
  1627.      */
  1628.     public static function find_by_sql($sql, $values=null)
  1629.     {
  1630.         return static::table()->find_by_sql($sql, $values, true);
  1631.     }
  1632.  
  1633.     /**
  1634.      * Helper method to run arbitrary queries against the model's database connection.
  1635.      *
  1636.      * @param string $sql SQL to execute
  1637.      * @param array $values Bind values, if any, for the query
  1638.      * @return object A PDOStatement object
  1639.      */
  1640.     public static function query($sql, $values=null)
  1641.     {
  1642.         return static::connection()->query($sql, $values);
  1643.     }
  1644.  
  1645.     /**
  1646.      * Determines if the specified array is a valid ActiveRecord options array.
  1647.      *
  1648.      * @param array $array An options array
  1649.      * @param bool $throw True to throw an exception if not valid
  1650.      * @return boolean True if valid otherwise valse
  1651.      * @throws {@link ActiveRecordException} if the array contained any invalid options
  1652.      */
  1653.     public static function is_options_hash($array, $throw=true)
  1654.     {
  1655.         if (is_hash($array))
  1656.         {
  1657.             $keys = array_keys($array);
  1658.             $diff = array_diff($keys,self::$VALID_OPTIONS);
  1659.  
  1660.             if (!empty($diff) && $throw)
  1661.                 throw new ActiveRecordException("Unknown key(s): " . join(', ',$diff));
  1662.  
  1663.             $intersect = array_intersect($keys,self::$VALID_OPTIONS);
  1664.  
  1665.             if (!empty($intersect))
  1666.                 return true;
  1667.         }
  1668.         return false;
  1669.     }
  1670.  
  1671.     /**
  1672.      * Returns a hash containing the names => values of the primary key.
  1673.      *
  1674.      * @internal This needs to eventually support composite keys.
  1675.      * @param mixed $args Primary key value(s)
  1676.      * @return array An array in the form array(name => value, ...)
  1677.      */
  1678.     public static function pk_conditions($args)
  1679.     {
  1680.         $table = static::table();
  1681.         $ret = array($table->pk[0] => $args);
  1682.         return $ret;
  1683.     }
  1684.  
  1685.     /**
  1686.      * Pulls out the options hash from $array if any.
  1687.      *
  1688.      * @internal DO NOT remove the reference on $array.
  1689.      * @param array &$array An array
  1690.      * @return array A valid options array
  1691.      */
  1692.     public static function extract_and_validate_options(array &$array)
  1693.     {
  1694.         $options = array();
  1695.  
  1696.         if ($array)
  1697.         {
  1698.             $last = &$array[count($array)-1];
  1699.  
  1700.             try
  1701.             {
  1702.                 if (self::is_options_hash($last))
  1703.                 {
  1704.                     array_pop($array);
  1705.                     $options = $last;
  1706.                 }
  1707.             }
  1708.             catch (ActiveRecordException $e)
  1709.             {
  1710.                 if (!is_hash($last))
  1711.                     throw $e;
  1712.  
  1713.                 $options = array('conditions' => $last);
  1714.             }
  1715.         }
  1716.         return $options;
  1717.     }
  1718.  
  1719.     /**
  1720.      * Returns a JSON representation of this model.
  1721.      *
  1722.      * @see Serialization
  1723.      * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
  1724.      * @return string JSON representation of the model
  1725.      */
  1726.     public function to_json(array $options=array())
  1727.     {
  1728.         return $this->serialize('Json', $options);
  1729.     }
  1730.  
  1731.     /**
  1732.      * Returns an XML representation of this model.
  1733.      *
  1734.      * @see Serialization
  1735.      * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)
  1736.      * @return string XML representation of the model
  1737.      */
  1738.     public function to_xml(array $options=array())
  1739.     {
  1740.         return $this->serialize('Xml', $options);
  1741.     }
  1742.  
  1743.    /**
  1744.    * Returns an CSV representation of this model.
  1745.    * Can take optional delimiter and enclosure
  1746.    * (defaults are , and double quotes)
  1747.    *
  1748.    * Ex:
  1749.    * <code>
  1750.    * ActiveRecord\CsvSerializer::$delimiter=';';
  1751.    * ActiveRecord\CsvSerializer::$enclosure='';
  1752.    * YourModel::find('first')->to_csv(array('only'=>array('name','level')));
  1753.    * returns: Joe,2
  1754.    *
  1755.    * YourModel::find('first')->to_csv(array('only_header'=>true,'only'=>array('name','level')));
  1756.    * returns: name,level
  1757.    * </code>
  1758.    *
  1759.    * @see Serialization
  1760.    * @param array $options An array containing options for csv serialization (see {@link Serialization} for valid options)
  1761.    * @return string CSV representation of the model
  1762.    */
  1763.   public function to_csv(array $options=array())
  1764.   {
  1765.     return $this->serialize('Csv', $options);
  1766.   }
  1767.  
  1768.     /**
  1769.      * Returns an Array representation of this model.
  1770.      *
  1771.      * @see Serialization
  1772.      * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
  1773.      * @return array Array representation of the model
  1774.      */
  1775.     public function to_array(array $options=array())
  1776.     {
  1777.         return $this->serialize('Array', $options);
  1778.     }
  1779.  
  1780.     /**
  1781.      * Creates a serializer based on pre-defined to_serializer()
  1782.      *
  1783.      * An options array can take the following parameters:
  1784.      *
  1785.      * <ul>
  1786.      * <li><b>only:</b> a string or array of attributes to be included.</li>
  1787.      * <li><b>excluded:</b> a string or array of attributes to be excluded.</li>
  1788.      * <li><b>methods:</b> a string or array of methods to invoke. The method's name will be used as a key for the final attributes array
  1789.      * along with the method's returned value</li>
  1790.      * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
  1791.      * </ul>
  1792.      *
  1793.      * @param string $type Either Xml, Json, Csv or Array
  1794.      * @param array $options Options array for the serializer
  1795.      * @return string Serialized representation of the model
  1796.      */
  1797.     private function serialize($type, $options)
  1798.     {
  1799.         require_once 'Serialization.php';
  1800.         $class = "ActiveRecord\\{$type}Serializer";
  1801.         $serializer = new $class($this, $options);
  1802.         return $serializer->to_s();
  1803.     }
  1804.  
  1805.     /**
  1806.      * Invokes the specified callback on this model.
  1807.      *
  1808.      * @param string $method_name Name of the call back to run.
  1809.      * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
  1810.      * @return boolean True if invoked or null if not
  1811.      */
  1812.     private function invoke_callback($method_name, $must_exist=true)
  1813.     {
  1814.         return static::table()->callback->invoke($this,$method_name,$must_exist);
  1815.     }
  1816.  
  1817.     /**
  1818.      * Executes a block of code inside a database transaction.
  1819.      *
  1820.      * <code>
  1821.      * YourModel::transaction(function()
  1822.      * {
  1823.      *   YourModel::create(array("name" => "blah"));
  1824.      * });
  1825.      * </code>
  1826.      *
  1827.      * If an exception is thrown inside the closure the transaction will
  1828.      * automatically be rolled back. You can also return false from your
  1829.      * closure to cause a rollback:
  1830.      *
  1831.      * <code>
  1832.      * YourModel::transaction(function()
  1833.      * {
  1834.      *   YourModel::create(array("name" => "blah"));
  1835.      *   throw new Exception("rollback!");
  1836.      * });
  1837.      *
  1838.      * YourModel::transaction(function()
  1839.      * {
  1840.      *   YourModel::create(array("name" => "blah"));
  1841.      *   return false; # rollback!
  1842.      * });
  1843.      * </code>
  1844.      *
  1845.      * @param Closure $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.
  1846.      * @return boolean True if the transaction was committed, False if rolled back.
  1847.      */
  1848.     public static function transaction($closure)
  1849.     {
  1850.         $connection = static::connection();
  1851.  
  1852.         try
  1853.         {
  1854.             $connection->transaction();
  1855.  
  1856.             if ($closure() === false)
  1857.             {
  1858.                 $connection->rollback();
  1859.                 return false;
  1860.             }
  1861.             else
  1862.                 $connection->commit();
  1863.         }
  1864.         catch (\Exception $e)
  1865.         {
  1866.             $connection->rollback();
  1867.             throw $e;
  1868.         }
  1869.         return true;
  1870.     }
  1871. };
  1872. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement