· 8 years ago · Jul 30, 2018, 09:22 AM
1<?php
2
3/**
4 * Record is the object notation for a row in a database table.
5 *
6 * When working with objects stored in the database we will first generate a Record
7 * from them so that we can call member functions on them.
8 *
9 * Any object we use will extend Record. Record will not be instantiated directly.
10 *
11 */
12
13abstract class Record{
14
15 /**
16 * This defines the name of the table that this particular instance of Record resides in.
17 * By convention the table name should always be all lower canse and plural.
18 *
19 * "records" is OK. "Record" is not.
20 */
21 public static $table = 'records';
22
23 /**
24 * This array maps the name of the relationship (ie: "posts") to the class of Records that make up the
25 * relationship (ie: "Post"). This enables you to call $user->posts and get the list of posts for whom
26 * the user_id column is equal to the id of this object. todo
27 */
28 public static $has_many = array();
29
30 /**
31 * This array contains the class names (ie: "Record") of any classes which belong to this REcord
32 * in a has_one / belongs_to relationship. todo
33 */
34 public static $has_one = array();
35
36 /**
37 * This associative array maps the name of a relationship (ie: "author") to the class of Records
38 * that make up the relationship (ie: "User"). todo
39 */
40 public static $belongs_to = array();
41
42 /**
43 * This contains all of the properties read from the database. This array serves as the direct link
44 * when things are updated in this array we save those changes to the database and when they are read
45 * from the database we store them here.
46 */
47 public $properties = array();
48
49 /**
50 * Boolean flag related to whether or not this object was read from the database or created
51 * from scratch. Necessary for determining whether or not to use an INSERT or UPDATE query when
52 * updating the record.
53 */
54 protected $fromDatabase;
55
56 /**
57 * Takes in an array of attributes and instantiates a new instance of this Record. Typically called
58 * by the find function to create instances of the object, but can also be uesd to create a new object
59 * prior to saving it to the database.
60 */
61 public function __construct($attributes = array(), $fromDatabase = false){
62 $this->properties = $attributes;
63 $this->fromDatabase = $fromDatabase;
64 }
65
66 /**
67 * Saves this instance of the Record to the database. Automatically determines whether it exists
68 * already and if it does, update the record, otherwise create a new one.
69 */
70 public function save(){
71 if ($this->fromDatabase){
72 $this->properties = array_merge($this->properties, array("edited_at" => mysqlTime(time())));
73 updateQuery(static::$table, $this->properties, array("id" => $this->id));
74 }
75 else{
76 $this->properties = array_merge($this->properties, array("edited_at" => mysqlTime(time()), "created_at" => mysqlTime(time())));
77 insertQuery(static::$table, $this->properties);
78 $this->fromDatabase = true; //it's in there now.
79 }
80 }
81
82 /**
83 * Removes this record from the database
84 */
85 public function delete(){
86 deleteQuery(static::$table, array("id" => $this->id));
87 $this->fromDatabase = false;
88 }
89
90 /**
91 * Updates attributes as given by the keys of $attributes to the values stored in the array
92 * This function will call save() and therefore immediately update the database unless the $lazy
93 * flag is set to true.
94 */
95 public function updateAttributes($attributes, $lazy = false){
96 $this->properties = array_merge($this->properties, $attributes);
97 if (!$lazy) $this->save();
98 }
99
100 /**
101 * Provided for convenience when setting a single attribute. Alias for updateAttributes with a single
102 * element array.
103 */
104 public function updateAttribute($attribute, $value, $lazy = false){
105 $this->updateAttributes(array($attribute => $value), $lazy);
106 }
107
108 /**
109 * Static function that returns an array containing all of the Records of this class in the
110 * database that match the conditional critera.
111 * @param array $conditionals the conditionals to search on.
112 */
113 public static function find($conditionals = array()){
114 //todo: replace with your selectQuery implementation
115 $queryResults = selectQuery(static::$table, array('*'), $conditionals);
116 $classname = static::classname();
117 $return = array(); //instantiate it so that it's an empty array if none are found rather than null
118 foreach ($queryResults as $q) $return[] = new $classname($q, true);
119 return $return;
120 }
121
122 /**
123 * Returns a single result (the first one) based on the conditional array passed in. This is
124 * simply shorthand for calling find() and then taking the [0]th element. Provided for convenience
125 * when results are anticipated to include only a single result (such as an id lookup)
126 */
127 public static function findOne($conditionals = array()){
128 $all = static::find($conditionals);
129 if (count($all) > 0) return $all[0];
130 else return false;
131 }
132
133 /**
134 * Returns the owner of the given classname in a has_many / belongs_to relationship where the calling
135 * object belongs_to the owner that will be returned.
136 *
137 * Returns false on error (owner could not be found)
138 */
139 public function getOwner($attributeName){
140 if (!in_array($attributeName, array_keys(static::$belongs_to))) return false;
141 $belongs_to = static::$belongs_to;
142 $classname = $belongs_to[$attributeName];
143
144 $remoteForeignKey = $attributeName."_id"; //get the name of the foreign key column
145 //$this->$remoteForeignKey is going to be the same as:
146 // $this->properties[$remoteForeignKey]
147 //which is going to evaluate like:
148 // $this->properties['user_id']
149 //which makes our conditional array something like
150 // array("id" => "17")
151 $owner = $classname::findOne(array("id" => $this->$remoteForeignKey));
152 return $owner;
153 }
154
155 /**
156 * Returns an array of Records of the given subclass related through as belongs_to / has_many relationship with
157 * this object.
158 *
159 */
160 public function getMany($attributeName){
161 //if its not in the array, return false
162
163 $has_many = static::$has_many;
164 $classname = $has_many[$attributeName];
165
166 /**
167 * TODO TODO TODO TODO TODO
168 *
169 * This is broken. This method will not allow for multiple relationships to the same class.
170 *
171 * Post has_many editors ("User")
172 * Post has_many commentors ("User")
173 * Is going to cause problems. We need a better solution.
174 */
175
176 $otherSide = $classname::$belongs_to;
177 $keys = array_keys($otherSide);
178 $values = array_values($otherSide);
179 for($i = 0; $i < count($keys); $i++){
180 if ($values[$i] == get_class($this)){
181 $remoteAttribute = $keys[$i];
182 }
183 }
184
185 $return = $classname::find(array($remoteAttribute."_id" => $this->id));
186 return $return;
187 }
188
189 /**
190 * Used to fetch the classname fo the calling class. The PHP constant __CLASS__ will always
191 * return "Record" even when called from a class that extends it. This function will return "Post"
192 * if called from the Post class.
193 */
194 private static function classname(){
195 return get_called_class();
196 }
197
198 /**
199 * __get is a MAGIC METHOD. Anytime there is a call for a property that is inaccessable (private or
200 * does not exist, such as "$record->id", this function will be called instead. This allows us to get
201 * information out of the properties array as though they existed as simple attributes
202 */
203 public function __get($name){
204 if (in_array($name, array_keys(static::$has_many))) return $this->getMany($name);
205 if (in_array($name, array_keys(static::$belongs_to))) return $this->getOwner($name);
206 return $this->properties[$name];
207 }
208
209 /**
210 * __set is a MAGIC METHOD. Anytime there is a call to set a property that is inaccessable (private or
211 * does not exist, such as "$record->title", this fucntion will be called instead. This allows us to set
212 * information in the properties array as though it existed as a simple attribute.
213 *
214 * Note! This does not save the record! Use this function sparingly! (or make it save the record)
215 */
216 public function __set($name, $value){
217 $this->updateAttribute($name, $value);
218 }
219}
220
221?>