my-simon 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.document +5 -0
  2. data/.gitignore.orig +55 -0
  3. data/DOC/Launch Check List.docx +0 -0
  4. data/DOC/Launch Check List.pdf +0 -0
  5. data/Gemfile +14 -0
  6. data/Gemfile.lock +44 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +21 -0
  9. data/README.rdoc +19 -0
  10. data/Rakefile +46 -0
  11. data/VERSION +1 -0
  12. data/build/rakefile.rb +12 -0
  13. data/config.rb +24 -0
  14. data/lib/simon.rb +0 -5
  15. data/my-simon.gemspec +141 -0
  16. data/scaffolding/simon/controller.tpl +9 -0
  17. data/scaffolding/simon/model.tpl +10 -0
  18. data/scaffolding/simon/view.tpl +5 -0
  19. data/scaffolding/standards/html_template.html +44 -0
  20. data/scaffolding/standards/html_template.php +44 -0
  21. data/scaffolding/standards/jquery_plugin_template.js +45 -0
  22. data/scaffolding/standards/js_template.js +43 -0
  23. data/simon +282 -0
  24. data/test/helper.rb +18 -0
  25. data/test/test_simon.rb +7 -0
  26. data/www/.htaccess +488 -0
  27. data/www/404.html +32 -0
  28. data/www/crossdomain.xml +25 -0
  29. data/www/favicon.ico +0 -0
  30. data/www/index.php +17 -0
  31. data/www/lib/js/homePage.js +43 -0
  32. data/www/lib/js/jquery/JQbook.js +809 -0
  33. data/www/lib/js/jquery/jquery-1.8.0.min.js +27 -0
  34. data/www/lib/js/jquery/jquery.alphanumeric.js +82 -0
  35. data/www/lib/js/jquery/jquery.cookie.js +96 -0
  36. data/www/lib/js/jquery/jquery.easing.1.3.js +207 -0
  37. data/www/lib/js/main.js +117 -0
  38. data/www/lib/js/master.js +6 -0
  39. data/www/lib/js/plugins/handlebars-1.0.rc.1.js +1920 -0
  40. data/www/lib/js/plugins/modernizr-1.7.min.js +2 -0
  41. data/www/lib/js/plugins/swfobject.js +777 -0
  42. data/www/lib/js/plugins_mod/README.txt +3 -0
  43. data/www/lib/php/app.php +53 -0
  44. data/www/lib/php/controller/index.php +10 -0
  45. data/www/lib/php/controller/regex.php +23 -0
  46. data/www/lib/php/model/Book.php +8 -0
  47. data/www/lib/php/model/Model.php +8 -0
  48. data/www/lib/php/plugins/php-activerecord/ActiveRecord.php +44 -0
  49. data/www/lib/php/plugins/php-activerecord/lib/CallBack.php +226 -0
  50. data/www/lib/php/plugins/php-activerecord/lib/Column.php +155 -0
  51. data/www/lib/php/plugins/php-activerecord/lib/Config.php +288 -0
  52. data/www/lib/php/plugins/php-activerecord/lib/Connection.php +456 -0
  53. data/www/lib/php/plugins/php-activerecord/lib/ConnectionManager.php +38 -0
  54. data/www/lib/php/plugins/php-activerecord/lib/DateTime.php +45 -0
  55. data/www/lib/php/plugins/php-activerecord/lib/Exceptions.php +137 -0
  56. data/www/lib/php/plugins/php-activerecord/lib/Expressions.php +183 -0
  57. data/www/lib/php/plugins/php-activerecord/lib/Inflector.php +115 -0
  58. data/www/lib/php/plugins/php-activerecord/lib/Model.php +1673 -0
  59. data/www/lib/php/plugins/php-activerecord/lib/Reflections.php +86 -0
  60. data/www/lib/php/plugins/php-activerecord/lib/Relationship.php +637 -0
  61. data/www/lib/php/plugins/php-activerecord/lib/SQLBuilder.php +396 -0
  62. data/www/lib/php/plugins/php-activerecord/lib/Serialization.php +302 -0
  63. data/www/lib/php/plugins/php-activerecord/lib/Singleton.php +57 -0
  64. data/www/lib/php/plugins/php-activerecord/lib/Table.php +547 -0
  65. data/www/lib/php/plugins/php-activerecord/lib/Utils.php +351 -0
  66. data/www/lib/php/plugins/php-activerecord/lib/Validations.php +833 -0
  67. data/www/lib/php/plugins/php-activerecord/lib/adapters/MysqlAdapter.php +73 -0
  68. data/www/lib/php/plugins/php-activerecord/lib/adapters/OciAdapter.php +121 -0
  69. data/www/lib/php/plugins/php-activerecord/lib/adapters/PgsqlAdapter.php +104 -0
  70. data/www/lib/php/plugins/php-activerecord/lib/adapters/SqliteAdapter.php +81 -0
  71. data/www/lib/php/system/Config.php +174 -0
  72. data/www/lib/php/system/config.routes.php +29 -0
  73. data/www/lib/php/system/router.php +220 -0
  74. data/www/lib/php/template/footer.php +59 -0
  75. data/www/lib/php/template/header.php +74 -0
  76. data/www/lib/php/view/index.php +5 -0
  77. data/www/media/images/facebook_share.jpg +0 -0
  78. data/www/robots.txt +5 -0
  79. data/www/sandbox/readme.txt +3 -0
  80. data/www/sass/javascript.scss +1 -0
  81. data/www/sass/layout.scss +128 -0
  82. data/www/sass/master.scss +4 -0
  83. data/www/sass/reset.scss +47 -0
  84. data/www/sass/typography.scss +24 -0
  85. data/www/styles/javascript.css +1 -0
  86. data/www/styles/layout.css +186 -0
  87. data/www/styles/master.css +4 -0
  88. data/www/styles/reset.css +60 -0
  89. data/www/styles/typography.css +24 -0
  90. metadata +179 -7
@@ -0,0 +1,1673 @@
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
+ * 'the_first_name' => 'first_name',
163
+ * 'the_last_name' => 'last_name');
164
+ * }
165
+ *
166
+ * $person = Person::first();
167
+ * $person->the_first_name = 'Tito';
168
+ * echo $person->the_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
+ * @var array
204
+ */
205
+ static $attr_protected = array();
206
+
207
+ /**
208
+ * Delegates calls to a relationship.
209
+ *
210
+ * <code>
211
+ * class Person extends ActiveRecord\Model {
212
+ * static $belongs_to = array(array('venue'),array('host'));
213
+ * static $delegate = array(
214
+ * array('name', 'state', 'to' => 'venue'),
215
+ * array('name', 'to' => 'host', 'prefix' => 'woot'));
216
+ * }
217
+ * </code>
218
+ *
219
+ * Can then do:
220
+ *
221
+ * <code>
222
+ * $person->state # same as calling $person->venue->state
223
+ * $person->name # same as calling $person->venue->name
224
+ * $person->woot_name # same as calling $person->host->name
225
+ * </code>
226
+ *
227
+ * @var array
228
+ */
229
+ static $delegate = array();
230
+
231
+ /**
232
+ * Define customer setters methods for the model.
233
+ *
234
+ * You can also use this to define custom setters for attributes as well.
235
+ *
236
+ * <code>
237
+ * class User extends ActiveRecord\Model {
238
+ * static $setters = array('password','more','even_more');
239
+ *
240
+ * # now to define the setter methods. Note you must
241
+ * # prepend set_ to your method name:
242
+ * function set_password($plaintext) {
243
+ * $this->encrypted_password = md5($plaintext);
244
+ * }
245
+ * }
246
+ *
247
+ * $user = new User();
248
+ * $user->password = 'plaintext'; # will call $user->set_password('plaintext')
249
+ * </code>
250
+ *
251
+ * If you define a custom setter with the same name as an attribute then you
252
+ * will need to use assign_attribute() to assign the value to the attribute.
253
+ * This is necessary due to the way __set() works.
254
+ *
255
+ * For example, assume 'name' is a field on the table and we're defining a
256
+ * custom setter for 'name':
257
+ *
258
+ * <code>
259
+ * class User extends ActiveRecord\Model {
260
+ * static $setters = array('name');
261
+ *
262
+ * # INCORRECT way to do it
263
+ * # function set_name($name) {
264
+ * # $this->name = strtoupper($name);
265
+ * # }
266
+ *
267
+ * function set_name($name) {
268
+ * $this->assign_attribute('name',strtoupper($name));
269
+ * }
270
+ * }
271
+ *
272
+ * $user = new User();
273
+ * $user->name = 'bob';
274
+ * echo $user->name; # => BOB
275
+ * </code>
276
+ *
277
+ * @var array
278
+ */
279
+ static $setters = array();
280
+
281
+ /**
282
+ * Define customer getter methods for the model.
283
+ *
284
+ * <code>
285
+ * class User extends ActiveRecord\Model {
286
+ * static $getters = array('middle_initial','more','even_more');
287
+ *
288
+ * # now to define the getter method. Note you must
289
+ * # prepend get_ to your method name:
290
+ * function get_middle_initial() {
291
+ * return $this->middle_name{0};
292
+ * }
293
+ * }
294
+ *
295
+ * $user = new User();
296
+ * echo $user->middle_name; # will call $user->get_middle_name()
297
+ * </code>
298
+ *
299
+ * If you define a custom getter with the same name as an attribute then you
300
+ * will need to use read_attribute() to get the attribute's value.
301
+ * This is necessary due to the way __get() works.
302
+ *
303
+ * For example, assume 'name' is a field on the table and we're defining a
304
+ * custom getter for 'name':
305
+ *
306
+ * <code>
307
+ * class User extends ActiveRecord\Model {
308
+ * static $getters = array('name');
309
+ *
310
+ * # INCORRECT way to do it
311
+ * # function get_name() {
312
+ * # return strtoupper($this->name);
313
+ * # }
314
+ *
315
+ * function get_name() {
316
+ * return strtoupper($this->read_attribute('name'));
317
+ * }
318
+ * }
319
+ *
320
+ * $user = new User();
321
+ * $user->name = 'bob';
322
+ * echo $user->name; # => BOB
323
+ * </code>
324
+ *
325
+ * @var array
326
+ */
327
+ static $getters = array();
328
+
329
+ /**
330
+ * Constructs a model.
331
+ *
332
+ * When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)
333
+ * then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given
334
+ * $attributes will be mapped via set_attributes_via_mass_assignment.
335
+ *
336
+ * <code>
337
+ * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
338
+ * </code>
339
+ *
340
+ * @param array $attributes Hash containing names and values to mass assign to the model
341
+ * @param boolean $guard_attributes Set to true to guard attributes
342
+ * @param boolean $instantiating_via_find Set to true if this model is being created from a find call
343
+ * @param boolean $new_record Set to true if this should be considered a new record
344
+ * @return Model
345
+ */
346
+ public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
347
+ {
348
+ $this->__new_record = $new_record;
349
+
350
+ // initialize attributes applying defaults
351
+ if (!$instantiating_via_find)
352
+ {
353
+ foreach (static::table()->columns as $name => $meta)
354
+ $this->attributes[$meta->inflected_name] = $meta->default;
355
+ }
356
+
357
+ $this->set_attributes_via_mass_assignment($attributes, $guard_attributes);
358
+
359
+ // since all attribute assignment now goes thru assign_attributes() we want to reset
360
+ // dirty if instantiating via find since nothing is really dirty when doing that
361
+ if ($instantiating_via_find)
362
+ $this->__dirty = array();
363
+
364
+ $this->invoke_callback('after_construct',false);
365
+ }
366
+
367
+ /**
368
+ * Magic method which delegates to read_attribute(). This handles firing off getter methods,
369
+ * as they are not checked/invoked inside of read_attribute(). This circumvents the problem with
370
+ * a getter being accessed with the same name as an actual attribute.
371
+ *
372
+ * @see read_attribute()
373
+ * @param string $name Name of an attribute
374
+ * @return mixed The value of the attribute
375
+ */
376
+ public function &__get($name)
377
+ {
378
+ // check for getter
379
+ if (in_array("get_$name",static::$getters))
380
+ {
381
+ $name = "get_$name";
382
+ $value = $this->$name();
383
+ return $value;
384
+ }
385
+
386
+ return $this->read_attribute($name);
387
+ }
388
+
389
+ /**
390
+ * Determines if an attribute exists for this {@link Model}.
391
+ *
392
+ * @param string $attribute_name
393
+ * @return boolean
394
+ */
395
+ public function __isset($attribute_name)
396
+ {
397
+ return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);
398
+ }
399
+
400
+ /**
401
+ * Magic allows un-defined attributes to set via $attributes
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 (in_array("set_$name",static::$setters))
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
+ foreach (static::$delegate as &$item)
423
+ {
424
+ if (($delegated_name = $this->is_delegated($name,$item)))
425
+ return $this->$item['to']->$delegated_name = $value;
426
+ }
427
+
428
+ throw new UndefinedPropertyException(get_called_class(),$name);
429
+ }
430
+
431
+ public function __wakeup()
432
+ {
433
+ // make sure the models Table instance gets initialized when waking up
434
+ static::table();
435
+ }
436
+
437
+ /**
438
+ * Assign a value to an attribute.
439
+ *
440
+ * @param string $name Name of the attribute
441
+ * @param mixed &$value Value of the attribute
442
+ * @return mixed the attribute value
443
+ */
444
+ public function assign_attribute($name, $value)
445
+ {
446
+ $table = static::table();
447
+
448
+ if (array_key_exists($name,$table->columns) && !is_object($value))
449
+ $value = $table->columns[$name]->cast($value,static::connection());
450
+
451
+ // convert php's \DateTime to ours
452
+ if ($value instanceof \DateTime)
453
+ $value = new DateTime($value->format('Y-m-d H:i:s T'));
454
+
455
+ // make sure DateTime values know what model they belong to so
456
+ // dirty stuff works when calling set methods on the DateTime object
457
+ if ($value instanceof DateTime)
458
+ $value->attribute_of($this,$name);
459
+
460
+ $this->attributes[$name] = $value;
461
+ $this->flag_dirty($name);
462
+ return $value;
463
+ }
464
+
465
+ /**
466
+ * Retrieves an attribute's value or a relationship object based on the name passed. If the attribute
467
+ * accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is
468
+ * for the primary key.
469
+ *
470
+ * @param string $name Name of an attribute
471
+ * @return mixed The value of the attribute
472
+ * @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...
473
+ */
474
+ public function &read_attribute($name)
475
+ {
476
+ // check for aliased attribute
477
+ if (array_key_exists($name, static::$alias_attribute))
478
+ $name = static::$alias_attribute[$name];
479
+
480
+ // check for attribute
481
+ if (array_key_exists($name,$this->attributes))
482
+ return $this->attributes[$name];
483
+
484
+ // check relationships if no attribute
485
+ if (array_key_exists($name,$this->__relationships))
486
+ return $this->__relationships[$name];
487
+
488
+ $table = static::table();
489
+
490
+ // this may be first access to the relationship so check Table
491
+ if (($relationship = $table->get_relationship($name)))
492
+ {
493
+ $this->__relationships[$name] = $relationship->load($this);
494
+ return $this->__relationships[$name];
495
+ }
496
+
497
+ if ($name == 'id')
498
+ {
499
+ if (count($this->get_primary_key()) > 1)
500
+ throw new Exception("TODO composite key support");
501
+
502
+ if (isset($this->attributes[$table->pk[0]]))
503
+ return $this->attributes[$table->pk[0]];
504
+ }
505
+
506
+ //do not remove - have to return null by reference in strict mode
507
+ $null = null;
508
+
509
+ foreach (static::$delegate as &$item)
510
+ {
511
+ if (($delegated_name = $this->is_delegated($name,$item)))
512
+ {
513
+ $to = $item['to'];
514
+ if ($this->$to)
515
+ {
516
+ $val =& $this->$to->$delegated_name;
517
+ return $val;
518
+ }
519
+ else
520
+ return $null;
521
+ }
522
+ }
523
+
524
+ throw new UndefinedPropertyException(get_called_class(),$name);
525
+ }
526
+
527
+ /**
528
+ * Flags an attribute as dirty.
529
+ *
530
+ * @param string $name Attribute name
531
+ */
532
+ public function flag_dirty($name)
533
+ {
534
+ if (!$this->__dirty)
535
+ $this->__dirty = array();
536
+
537
+ $this->__dirty[$name] = true;
538
+ }
539
+
540
+ /**
541
+ * Returns hash of attributes that have been modified since loading the model.
542
+ *
543
+ * @return mixed null if no dirty attributes otherwise returns array of dirty attributes.
544
+ */
545
+ public function dirty_attributes()
546
+ {
547
+ if (!$this->__dirty)
548
+ return null;
549
+
550
+ $dirty = array_intersect_key($this->attributes,$this->__dirty);
551
+ return !empty($dirty) ? $dirty : null;
552
+ }
553
+
554
+ /**
555
+ * Returns a copy of the model's attributes hash.
556
+ *
557
+ * @return array A copy of the model's attribute data
558
+ */
559
+ public function attributes()
560
+ {
561
+ return $this->attributes;
562
+ }
563
+
564
+ /**
565
+ * Retrieve the primary key name.
566
+ *
567
+ * @return string The primary key for the model
568
+ */
569
+ public function get_primary_key()
570
+ {
571
+ return Table::load(get_class($this))->pk;
572
+ }
573
+
574
+ /**
575
+ * Returns the actual attribute name if $name is aliased.
576
+ *
577
+ * @param string $name An attribute name
578
+ * @return string
579
+ */
580
+ public function get_real_attribute_name($name)
581
+ {
582
+ if (array_key_exists($name,$this->attributes))
583
+ return $name;
584
+
585
+ if (array_key_exists($name,static::$alias_attribute))
586
+ return static::$alias_attribute[$name];
587
+
588
+ return null;
589
+ }
590
+
591
+ /**
592
+ * Returns array of validator data for this Model.
593
+ *
594
+ * Will return an array looking like:
595
+ *
596
+ * <code>
597
+ * array(
598
+ * 'name' => array(
599
+ * array('validator' => 'validates_presence_of'),
600
+ * array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),
601
+ * 'password' => array(
602
+ * array('validator' => 'validates_length_of', 'minimum' => 6))
603
+ * )
604
+ * );
605
+ * </code>
606
+ *
607
+ * @return array An array containing validator data for this model.
608
+ */
609
+ public function get_validation_rules()
610
+ {
611
+ require_once 'Validations.php';
612
+
613
+ $validator = new Validations($this);
614
+ return $validator->rules();
615
+ }
616
+
617
+ /**
618
+ * Returns an associative array containing values for all the attributes in $attributes
619
+ *
620
+ * @param array $attributes Array containing attribute names
621
+ * @return array A hash containing $name => $value
622
+ */
623
+ public function get_values_for($attributes)
624
+ {
625
+ $ret = array();
626
+
627
+ foreach ($attributes as $name)
628
+ {
629
+ if (array_key_exists($name,$this->attributes))
630
+ $ret[$name] = $this->attributes[$name];
631
+ }
632
+ return $ret;
633
+ }
634
+
635
+ /**
636
+ * Retrieves the name of the table for this Model.
637
+ *
638
+ * @return string
639
+ */
640
+ public static function table_name()
641
+ {
642
+ return static::table()->table;
643
+ }
644
+
645
+ /**
646
+ * Returns the attribute name on the delegated relationship if $name is
647
+ * delegated or null if not delegated.
648
+ *
649
+ * @param string $name Name of an attribute
650
+ * @param array $delegate An array containing delegate data
651
+ * @return delegated attribute name or null
652
+ */
653
+ private function is_delegated($name, &$delegate)
654
+ {
655
+ if ($delegate['prefix'] != '')
656
+ $name = substr($name,strlen($delegate['prefix'])+1);
657
+
658
+ if (is_array($delegate) && in_array($name,$delegate['delegate']))
659
+ return $name;
660
+
661
+ return null;
662
+ }
663
+
664
+ /**
665
+ * Determine if the model is in read-only mode.
666
+ *
667
+ * @return boolean
668
+ */
669
+ public function is_readonly()
670
+ {
671
+ return $this->__readonly;
672
+ }
673
+
674
+ /**
675
+ * Determine if the model is a new record.
676
+ *
677
+ * @return boolean
678
+ */
679
+ public function is_new_record()
680
+ {
681
+ return $this->__new_record;
682
+ }
683
+
684
+ /**
685
+ * Throws an exception if this model is set to readonly.
686
+ *
687
+ * @throws ActiveRecord\ReadOnlyException
688
+ * @param string $method_name Name of method that was invoked on model for exception message
689
+ */
690
+ private function verify_not_readonly($method_name)
691
+ {
692
+ if ($this->is_readonly())
693
+ throw new ReadOnlyException(get_class($this), $method_name);
694
+ }
695
+
696
+ /**
697
+ * Flag model as readonly.
698
+ *
699
+ * @param boolean $readonly Set to true to put the model into readonly mode
700
+ */
701
+ public function readonly($readonly=true)
702
+ {
703
+ $this->__readonly = $readonly;
704
+ }
705
+
706
+ /**
707
+ * Retrieve the connection for this model.
708
+ *
709
+ * @return Connection
710
+ */
711
+ public static function connection()
712
+ {
713
+ return static::table()->conn;
714
+ }
715
+
716
+ /**
717
+ * Returns the {@link Table} object for this model.
718
+ *
719
+ * Be sure to call in static scoping: static::table()
720
+ *
721
+ * @return Table
722
+ */
723
+ public static function table()
724
+ {
725
+ return Table::load(get_called_class());
726
+ }
727
+
728
+ /**
729
+ * Creates a model and saves it to the database.
730
+ *
731
+ * @param array $attributes Array of the models attributes
732
+ * @param boolean $validate True if the validators should be run
733
+ * @return Model
734
+ */
735
+ public static function create($attributes, $validate=true)
736
+ {
737
+ $class_name = get_called_class();
738
+ $model = new $class_name($attributes);
739
+ $model->save($validate);
740
+ return $model;
741
+ }
742
+
743
+ /**
744
+ * Save the model to the database.
745
+ *
746
+ * This function will automatically determine if an INSERT or UPDATE needs to occur.
747
+ * If a validation or a callback for this model returns false, then the model will
748
+ * not be saved and this will return false.
749
+ *
750
+ * If saving an existing model only data that has changed will be saved.
751
+ *
752
+ * @param boolean $validate Set to true or false depending on if you want the validators to run or not
753
+ * @return boolean True if the model was saved to the database otherwise false
754
+ */
755
+ public function save($validate=true)
756
+ {
757
+ $this->verify_not_readonly('save');
758
+ return $this->is_new_record() ? $this->insert($validate) : $this->update($validate);
759
+ }
760
+
761
+ /**
762
+ * Issue an INSERT sql statement for this model's attribute.
763
+ *
764
+ * @see save
765
+ * @param boolean $validate Set to true or false depending on if you want the validators to run or not
766
+ * @return boolean True if the model was saved to the database otherwise false
767
+ */
768
+ private function insert($validate=true)
769
+ {
770
+ $this->verify_not_readonly('insert');
771
+
772
+ if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
773
+ return false;
774
+
775
+ $table = static::table();
776
+
777
+ if (!($attributes = $this->dirty_attributes()))
778
+ $attributes = $this->attributes;
779
+
780
+ $pk = $this->get_primary_key();
781
+ $use_sequence = false;
782
+
783
+ if ($table->sequence && !isset($attributes[$pk[0]]))
784
+ {
785
+ if (($conn = static::connection()) instanceof OciAdapter)
786
+ {
787
+ // terrible oracle makes us select the nextval first
788
+ $attributes[$pk[0]] = $conn->get_next_sequence_value($table->sequence);
789
+ $table->insert($attributes);
790
+ $this->attributes[$pk[0]] = $attributes[$pk[0]];
791
+ }
792
+ else
793
+ {
794
+ // unset pk that was set to null
795
+ if (array_key_exists($pk[0],$attributes))
796
+ unset($attributes[$pk[0]]);
797
+
798
+ $table->insert($attributes,$pk[0],$table->sequence);
799
+ $use_sequence = true;
800
+ }
801
+ }
802
+ else
803
+ $table->insert($attributes);
804
+
805
+ // if we've got an autoincrementing/sequenced pk set it
806
+ if (count($pk) == 1)
807
+ {
808
+ $column = $table->get_column_by_inflected_name($pk[0]);
809
+
810
+ if ($column->auto_increment || $use_sequence)
811
+ $this->attributes[$pk[0]] = $table->conn->insert_id($table->sequence);
812
+ }
813
+
814
+ $this->invoke_callback('after_create',false);
815
+ $this->__new_record = false;
816
+ return true;
817
+ }
818
+
819
+ /**
820
+ * Issue an UPDATE sql statement for this model's dirty attributes.
821
+ *
822
+ * @see save
823
+ * @param boolean $validate Set to true or false depending on if you want the validators to run or not
824
+ * @return boolean True if the model was saved to the database otherwise false
825
+ */
826
+ private function update($validate=true)
827
+ {
828
+ $this->verify_not_readonly('update');
829
+
830
+ if ($validate && !$this->_validate())
831
+ return false;
832
+
833
+ if ($this->is_dirty())
834
+ {
835
+ $pk = $this->values_for_pk();
836
+
837
+ if (empty($pk))
838
+ throw new ActiveRecordException("Cannot update, no primary key defined for: " . get_called_class());
839
+
840
+ if (!$this->invoke_callback('before_update',false))
841
+ return false;
842
+
843
+ $dirty = $this->dirty_attributes();
844
+ static::table()->update($dirty,$pk);
845
+ $this->invoke_callback('after_update',false);
846
+ }
847
+
848
+ return true;
849
+ }
850
+
851
+ /**
852
+ * Deletes this model from the database and returns true if successful.
853
+ *
854
+ * @return boolean
855
+ */
856
+ public function delete()
857
+ {
858
+ $this->verify_not_readonly('delete');
859
+
860
+ $pk = $this->values_for_pk();
861
+
862
+ if (empty($pk))
863
+ throw new ActiveRecordException("Cannot delete, no primary key defined for: " . get_called_class());
864
+
865
+ if (!$this->invoke_callback('before_destroy',false))
866
+ return false;
867
+
868
+ static::table()->delete($pk);
869
+ $this->invoke_callback('after_destroy',false);
870
+
871
+ return true;
872
+ }
873
+
874
+ /**
875
+ * Helper that creates an array of values for the primary key(s).
876
+ *
877
+ * @return array An array in the form array(key_name => value, ...)
878
+ */
879
+ public function values_for_pk()
880
+ {
881
+ return $this->values_for(static::table()->pk);
882
+ }
883
+
884
+ /**
885
+ * Helper to return a hash of values for the specified attributes.
886
+ *
887
+ * @param array $attribute_names Array of attribute names
888
+ * @return array An array in the form array(name => value, ...)
889
+ */
890
+ public function values_for($attribute_names)
891
+ {
892
+ $filter = array();
893
+
894
+ foreach ($attribute_names as $name)
895
+ $filter[$name] = $this->$name;
896
+
897
+ return $filter;
898
+ }
899
+
900
+ /**
901
+ * Validates the model.
902
+ *
903
+ * @return boolean True if passed validators otherwise false
904
+ */
905
+ private function _validate()
906
+ {
907
+ require_once 'Validations.php';
908
+
909
+ $validator = new Validations($this);
910
+ $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
911
+
912
+ foreach (array('before_validation', "before_$validation_on") as $callback)
913
+ {
914
+ if (!$this->invoke_callback($callback,false))
915
+ return false;
916
+ }
917
+
918
+ $this->errors = $validator->validate();
919
+
920
+ foreach (array('after_validation', "after_$validation_on") as $callback)
921
+ $this->invoke_callback($callback,false);
922
+
923
+ if (!$this->errors->is_empty())
924
+ return false;
925
+
926
+ return true;
927
+ }
928
+
929
+ /**
930
+ * Returns true if the model has been modified.
931
+ *
932
+ * @return boolean true if modified
933
+ */
934
+ public function is_dirty()
935
+ {
936
+ return empty($this->__dirty) ? false : true;
937
+ }
938
+
939
+ /**
940
+ * Run validations on model and returns whether or not model passed validation.
941
+ *
942
+ * @see is_invalid
943
+ * @return boolean
944
+ */
945
+ public function is_valid()
946
+ {
947
+ return $this->_validate();
948
+ }
949
+
950
+ /**
951
+ * Runs validations and returns true if invalid.
952
+ *
953
+ * @see is_valid
954
+ * @return boolean
955
+ */
956
+ public function is_invalid()
957
+ {
958
+ return !$this->_validate();
959
+ }
960
+
961
+ /**
962
+ * Updates a model's timestamps.
963
+ */
964
+ public function set_timestamps()
965
+ {
966
+ $now = date('Y-m-d H:i:s');
967
+
968
+ if (isset($this->updated_at))
969
+ $this->updated_at = $now;
970
+
971
+ if (isset($this->created_at) && $this->is_new_record())
972
+ $this->created_at = $now;
973
+ }
974
+
975
+ /**
976
+ * Mass update the model with an array of attribute data and saves to the database.
977
+ *
978
+ * @param array $attributes An attribute data array in the form array(name => value, ...)
979
+ * @return boolean True if successfully updated and saved otherwise false
980
+ */
981
+ public function update_attributes($attributes)
982
+ {
983
+ $this->set_attributes($attributes);
984
+ return $this->save();
985
+ }
986
+
987
+ /**
988
+ * Updates a single attribute and saves the record without going through the normal validation procedure.
989
+ *
990
+ * @param string $name Name of attribute
991
+ * @param mixed $value Value of the attribute
992
+ * @return boolean True if successful otherwise false
993
+ */
994
+ public function update_attribute($name, $value)
995
+ {
996
+ $this->__set($name, $value);
997
+ return $this->update(false);
998
+ }
999
+
1000
+ /**
1001
+ * Mass update the model with data from an attributes hash.
1002
+ *
1003
+ * Unlike update_attributes() this method only updates the model's data
1004
+ * but DOES NOT save it to the database.
1005
+ *
1006
+ * @see update_attributes
1007
+ * @param array $attributes An array containing data to update in the form array(name => value, ...)
1008
+ */
1009
+ public function set_attributes(array $attributes)
1010
+ {
1011
+ $this->set_attributes_via_mass_assignment($attributes, true);
1012
+ }
1013
+
1014
+ /**
1015
+ * Passing $guard_attributes as true will throw an exception if an attribute does not exist.
1016
+ *
1017
+ * @throws ActiveRecord\UndefinedPropertyException
1018
+ * @param array $attributes An array in the form array(name => value, ...)
1019
+ * @param boolean $guard_attributes Flag of whether or not attributes should be guarded
1020
+ */
1021
+ private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
1022
+ {
1023
+ //access uninflected columns since that is what we would have in result set
1024
+ $table = static::table();
1025
+ $exceptions = array();
1026
+ $use_attr_accessible = !empty(static::$attr_accessible);
1027
+ $use_attr_protected = !empty(static::$attr_protected);
1028
+ $connection = static::connection();
1029
+
1030
+ foreach ($attributes as $name => $value)
1031
+ {
1032
+ // is a normal field on the table
1033
+ if (array_key_exists($name,$table->columns))
1034
+ {
1035
+ $value = $table->columns[$name]->cast($value,$connection);
1036
+ $name = $table->columns[$name]->inflected_name;
1037
+ }
1038
+
1039
+ if ($guard_attributes)
1040
+ {
1041
+ if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
1042
+ continue;
1043
+
1044
+ if ($use_attr_protected && in_array($name,static::$attr_protected))
1045
+ continue;
1046
+
1047
+ // set valid table data
1048
+ try {
1049
+ $this->$name = $value;
1050
+ } catch (UndefinedPropertyException $e) {
1051
+ $exceptions[] = $e->getMessage();
1052
+ }
1053
+ }
1054
+ else
1055
+ {
1056
+ // ignore OciAdapter's limit() stuff
1057
+ if ($name == 'ar_rnum__')
1058
+ continue;
1059
+
1060
+ // set arbitrary data
1061
+ $this->assign_attribute($name,$value);
1062
+ }
1063
+ }
1064
+
1065
+ if (!empty($exceptions))
1066
+ throw new UndefinedPropertyException(get_called_class(),$exceptions);
1067
+ }
1068
+
1069
+ /**
1070
+ * Add a model to the given named ($name) relationship.
1071
+ *
1072
+ * @internal This should <strong>only</strong> be used by eager load
1073
+ * @param Model $model
1074
+ * @param $name of relationship for this table
1075
+ * @return void
1076
+ */
1077
+ public function set_relationship_from_eager_load(Model $model=null, $name)
1078
+ {
1079
+ $table = static::table();
1080
+
1081
+ if (($rel = $table->get_relationship($name)))
1082
+ {
1083
+ if ($rel->is_poly())
1084
+ {
1085
+ // if the related model is null and it is a poly then we should have an empty array
1086
+ if (is_null($model))
1087
+ return $this->__relationships[$name] = array();
1088
+ else
1089
+ return $this->__relationships[$name][] = $model;
1090
+ }
1091
+ else
1092
+ return $this->__relationships[$name] = $model;
1093
+ }
1094
+
1095
+ throw new RelationshipException("Relationship named $name has not been declared for class: {$table->class->getName()}");
1096
+ }
1097
+
1098
+ /**
1099
+ * Reloads the attributes and relationships of this object from the database.
1100
+ *
1101
+ * @return Model
1102
+ */
1103
+ public function reload()
1104
+ {
1105
+ $this->__relationships = array();
1106
+ $pk = array_values($this->get_values_for($this->get_primary_key()));
1107
+
1108
+ $this->set_attributes($this->find($pk)->attributes);
1109
+ $this->reset_dirty();
1110
+
1111
+ return $this;
1112
+ }
1113
+
1114
+ public function __clone()
1115
+ {
1116
+ $this->__relationships = array();
1117
+ $this->reset_dirty();
1118
+ return $this;
1119
+ }
1120
+
1121
+ /**
1122
+ * Resets the dirty array.
1123
+ *
1124
+ * @see dirty_attributes
1125
+ */
1126
+ public function reset_dirty()
1127
+ {
1128
+ $this->__dirty = null;
1129
+ }
1130
+
1131
+ /**
1132
+ * A list of valid finder options.
1133
+ *
1134
+ * @var array
1135
+ */
1136
+ static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
1137
+
1138
+ /**
1139
+ * Enables the use of dynamic finders.
1140
+ *
1141
+ * Dynamic finders are just an easy way to do queries quickly without having to
1142
+ * specify an options array with conditions in it.
1143
+ *
1144
+ * <code>
1145
+ * SomeModel::find_by_first_name('Tito');
1146
+ * SomeModel::find_by_first_name_and_last_name('Tito','the Grief');
1147
+ * SomeModel::find_by_first_name_or_last_name('Tito','the Grief');
1148
+ * SomeModel::find_all_by_last_name('Smith');
1149
+ * SomeModel::count_by_name('Bob')
1150
+ * SomeModel::count_by_name_or_state('Bob','VA')
1151
+ * SomeModel::count_by_name_and_state('Bob','VA')
1152
+ * </code>
1153
+ *
1154
+ * You can also create the model if the find call returned no results:
1155
+ *
1156
+ * <code>
1157
+ * Person::find_or_create_by_name('Tito');
1158
+ *
1159
+ * # would be the equivalent of
1160
+ * if (!Person::find_by_name('Tito'))
1161
+ * Person::create(array('Tito'));
1162
+ * </code>
1163
+ *
1164
+ * Some other examples of find_or_create_by:
1165
+ *
1166
+ * <code>
1167
+ * Person::find_or_create_by_name_and_id('Tito',1);
1168
+ * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));
1169
+ * </code>
1170
+ *
1171
+ * @param string $method Name of method
1172
+ * @param mixed $args Method args
1173
+ * @return Model
1174
+ * @throws {@link ActiveRecordException} if invalid query
1175
+ * @see find
1176
+ */
1177
+ public static function __callStatic($method, $args)
1178
+ {
1179
+ $options = static::extract_and_validate_options($args);
1180
+ $create = false;
1181
+
1182
+ if (substr($method,0,17) == 'find_or_create_by')
1183
+ {
1184
+ $attributes = substr($method,17);
1185
+
1186
+ // can't take any finders with OR in it when doing a find_or_create_by
1187
+ if (strpos($attributes,'_or_') !== false)
1188
+ throw new ActiveRecordException("Cannot use OR'd attributes in find_or_create_by");
1189
+
1190
+ $create = true;
1191
+ $method = 'find_by' . substr($method,17);
1192
+ }
1193
+
1194
+ if (substr($method,0,7) === 'find_by')
1195
+ {
1196
+ $attributes = substr($method,8);
1197
+ $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,$attributes,$args,static::$alias_attribute);
1198
+
1199
+ if (!($ret = static::find('first',$options)) && $create)
1200
+ return static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));
1201
+
1202
+ return $ret;
1203
+ }
1204
+ elseif (substr($method,0,11) === 'find_all_by')
1205
+ {
1206
+ $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,substr($method,12),$args,static::$alias_attribute);
1207
+ return static::find('all',$options);
1208
+ }
1209
+ elseif (substr($method,0,8) === 'count_by')
1210
+ {
1211
+ $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,substr($method,9),$args,static::$alias_attribute);
1212
+ return static::count($options);
1213
+ }
1214
+
1215
+ throw new ActiveRecordException("Call to undefined method: $method");
1216
+ }
1217
+
1218
+ /**
1219
+ * Enables the use of build|create for associations.
1220
+ *
1221
+ * @param string $method Name of method
1222
+ * @param mixed $args Method args
1223
+ * @return mixed An instance of a given {@link AbstractRelationship}
1224
+ */
1225
+ public function __call($method, $args)
1226
+ {
1227
+ //check for build|create_association methods
1228
+ if (preg_match('/(build|create)_/', $method))
1229
+ {
1230
+ if (!empty($args))
1231
+ $args = $args[0];
1232
+
1233
+ $association_name = str_replace(array('build_', 'create_'), '', $method);
1234
+
1235
+ if (($association = static::table()->get_relationship($association_name)))
1236
+ {
1237
+ //access association to ensure that the relationship has been loaded
1238
+ //so that we do not double-up on records if we append a newly created
1239
+ $this->$association_name;
1240
+ $method = str_replace($association_name,'association', $method);
1241
+ return $association->$method($this, $args);
1242
+ }
1243
+ }
1244
+
1245
+ throw new ActiveRecordException("Call to undefined method: $method");
1246
+ }
1247
+
1248
+ /**
1249
+ * Alias for self::find('all').
1250
+ *
1251
+ * @see find
1252
+ * @return array array of records found
1253
+ */
1254
+ public static function all(/* ... */)
1255
+ {
1256
+ return call_user_func_array('static::find',array_merge(array('all'),func_get_args()));
1257
+ }
1258
+
1259
+ /**
1260
+ * Get a count of qualifying records.
1261
+ *
1262
+ * <code>
1263
+ * YourModel::count(array('conditions' => 'amount > 3.14159265'));
1264
+ * </code>
1265
+ *
1266
+ * @see find
1267
+ * @return int Number of records that matched the query
1268
+ */
1269
+ public static function count(/* ... */)
1270
+ {
1271
+ $args = func_get_args();
1272
+ $options = static::extract_and_validate_options($args);
1273
+ $options['select'] = 'COUNT(*)';
1274
+
1275
+ if (!empty($args))
1276
+ {
1277
+ if (is_hash($args[0]))
1278
+ $options['conditions'] = $args[0];
1279
+ else
1280
+ $options['conditions'] = call_user_func_array('static::pk_conditions',$args);
1281
+ }
1282
+
1283
+ $table = static::table();
1284
+ $sql = $table->options_to_sql($options);
1285
+ $values = $sql->get_where_values();
1286
+ return $table->conn->query_and_fetch_one($sql->to_s(),$values);
1287
+ }
1288
+
1289
+ /**
1290
+ * Determine if a record exists.
1291
+ *
1292
+ * <code>
1293
+ * SomeModel::exists(123);
1294
+ * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));
1295
+ * SomeModel::exists(array('id' => 123, 'name' => 'Tito'));
1296
+ * </code>
1297
+ *
1298
+ * @see find
1299
+ * @return boolean
1300
+ */
1301
+ public static function exists(/* ... */)
1302
+ {
1303
+ return call_user_func_array('static::count',func_get_args()) > 0 ? true : false;
1304
+ }
1305
+
1306
+ /**
1307
+ * Alias for self::find('first').
1308
+ *
1309
+ * @see find
1310
+ * @return Model The first matched record or null if not found
1311
+ */
1312
+ public static function first(/* ... */)
1313
+ {
1314
+ return call_user_func_array('static::find',array_merge(array('first'),func_get_args()));
1315
+ }
1316
+
1317
+ /**
1318
+ * Alias for self::find('last')
1319
+ *
1320
+ * @see find
1321
+ * @return Model The last matched record or null if not found
1322
+ */
1323
+ public static function last(/* ... */)
1324
+ {
1325
+ return call_user_func_array('static::find',array_merge(array('last'),func_get_args()));
1326
+ }
1327
+
1328
+ /**
1329
+ * Find records in the database.
1330
+ *
1331
+ * Finding by the primary key:
1332
+ *
1333
+ * <code>
1334
+ * # queries for the model with id=123
1335
+ * YourModel::find(123);
1336
+ *
1337
+ * # queries for model with id in(1,2,3)
1338
+ * YourModel::find(1,2,3);
1339
+ *
1340
+ * # finding by pk accepts an options array
1341
+ * YourModel::find(123,array('order' => 'name desc'));
1342
+ * </code>
1343
+ *
1344
+ * Finding by using a conditions array:
1345
+ *
1346
+ * <code>
1347
+ * YourModel::find('first', array('conditions' => array('name=?','Tito'),
1348
+ * 'order' => 'name asc'))
1349
+ * YourModel::find('all', array('conditions' => 'amount > 3.14159265'));
1350
+ * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));
1351
+ * </code>
1352
+ *
1353
+ * Finding by using a hash:
1354
+ *
1355
+ * <code>
1356
+ * YourModel::find(array('name' => 'Tito', 'id' => 1));
1357
+ * YourModel::find('first',array('name' => 'Tito', 'id' => 1));
1358
+ * YourModel::find('all',array('name' => 'Tito', 'id' => 1));
1359
+ * </code>
1360
+ *
1361
+ * An options array can take the following parameters:
1362
+ *
1363
+ * <ul>
1364
+ * <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>
1365
+ * <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>
1366
+ * <li><b>include:</b> TODO not implemented yet</li>
1367
+ * <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')),
1368
+ * array('name' => 'Tito', 'id' => 1)</li>
1369
+ * <li><b>limit:</b> Number of records to limit the query to</li>
1370
+ * <li><b>offset:</b> The row offset to return results from for the query</li>
1371
+ * <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>
1372
+ * <li><b>readonly:</b> Return all the models in readonly mode</li>
1373
+ * <li><b>group:</b> A SQL group by fragment</li>
1374
+ * </ul>
1375
+ *
1376
+ * @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched
1377
+ * @return mixed An array of records found if doing a find_all otherwise a
1378
+ * single Model object or null if it wasn't found. NULL is only return when
1379
+ * doing a first/last find. If doing an all find and no records matched this
1380
+ * will return an empty array.
1381
+ */
1382
+ public static function find(/* $type, $options */)
1383
+ {
1384
+ $class = get_called_class();
1385
+
1386
+ if (func_num_args() <= 0)
1387
+ throw new RecordNotFound("Couldn't find $class without an ID");
1388
+
1389
+ $args = func_get_args();
1390
+ $options = static::extract_and_validate_options($args);
1391
+ $num_args = count($args);
1392
+ $single = true;
1393
+
1394
+ if ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))
1395
+ {
1396
+ switch ($args[0])
1397
+ {
1398
+ case 'all':
1399
+ $single = false;
1400
+ break;
1401
+
1402
+ case 'last':
1403
+ if (!array_key_exists('order',$options))
1404
+ $options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';
1405
+ else
1406
+ $options['order'] = SQLBuilder::reverse_order($options['order']);
1407
+
1408
+ // fall thru
1409
+
1410
+ case 'first':
1411
+ $options['limit'] = 1;
1412
+ $options['offset'] = 0;
1413
+ break;
1414
+ }
1415
+
1416
+ $args = array_slice($args,1);
1417
+ $num_args--;
1418
+ }
1419
+ //find by pk
1420
+ elseif (1 === count($args) && 1 == $num_args)
1421
+ $args = $args[0];
1422
+
1423
+ // anything left in $args is a find by pk
1424
+ if ($num_args > 0 && !isset($options['conditions']))
1425
+ return static::find_by_pk($args, $options);
1426
+
1427
+ $options['mapped_names'] = static::$alias_attribute;
1428
+ $list = static::table()->find($options);
1429
+
1430
+ return $single ? (!empty($list) ? $list[0] : null) : $list;
1431
+ }
1432
+
1433
+ /**
1434
+ * Finder method which will find by a single or array of primary keys for this model.
1435
+ *
1436
+ * @see find
1437
+ * @param array $values An array containing values for the pk
1438
+ * @param array $options An options array
1439
+ * @return Model
1440
+ * @throws {@link RecordNotFound} if a record could not be found
1441
+ */
1442
+ public static function find_by_pk($values, $options)
1443
+ {
1444
+ $options['conditions'] = static::pk_conditions($values);
1445
+ $list = static::table()->find($options);
1446
+ $results = count($list);
1447
+
1448
+ if ($results != ($expected = count($values)))
1449
+ {
1450
+ $class = get_called_class();
1451
+
1452
+ if ($expected == 1)
1453
+ {
1454
+ if (!is_array($values))
1455
+ $values = array($values);
1456
+
1457
+ throw new RecordNotFound("Couldn't find $class with ID=" . join(',',$values));
1458
+ }
1459
+
1460
+ $values = join(',',$values);
1461
+ throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
1462
+ }
1463
+ return $expected == 1 ? $list[0] : $list;
1464
+ }
1465
+
1466
+ /**
1467
+ * Find using a raw SELECT query.
1468
+ *
1469
+ * <code>
1470
+ * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito'));
1471
+ * YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'");
1472
+ * </code>
1473
+ *
1474
+ * @param string $sql The raw SELECT query
1475
+ * @param array $values An array of values for any parameters that needs to be bound
1476
+ * @return array An array of models
1477
+ */
1478
+ public static function find_by_sql($sql, $values=null)
1479
+ {
1480
+ return static::table()->find_by_sql($sql, $values, true);
1481
+ }
1482
+
1483
+ /**
1484
+ * Determines if the specified array is a valid ActiveRecord options array.
1485
+ *
1486
+ * @param array $array An options array
1487
+ * @param bool $throw True to throw an exception if not valid
1488
+ * @return boolean True if valid otherwise valse
1489
+ * @throws {@link ActiveRecordException} if the array contained any invalid options
1490
+ */
1491
+ public static function is_options_hash($array, $throw=true)
1492
+ {
1493
+ if (is_hash($array))
1494
+ {
1495
+ $keys = array_keys($array);
1496
+ $diff = array_diff($keys,self::$VALID_OPTIONS);
1497
+
1498
+ if (!empty($diff) && $throw)
1499
+ throw new ActiveRecordException("Unknown key(s): " . join(', ',$diff));
1500
+
1501
+ $intersect = array_intersect($keys,self::$VALID_OPTIONS);
1502
+
1503
+ if (!empty($intersect))
1504
+ return true;
1505
+ }
1506
+ return false;
1507
+ }
1508
+
1509
+ /**
1510
+ * Returns a hash containing the names => values of the primary key.
1511
+ *
1512
+ * @internal This needs to eventually support composite keys.
1513
+ * @param mixed $args Primary key value(s)
1514
+ * @return array An array in the form array(name => value, ...)
1515
+ */
1516
+ public static function pk_conditions($args)
1517
+ {
1518
+ $table = static::table();
1519
+ $ret = array($table->pk[0] => $args);
1520
+ return $ret;
1521
+ }
1522
+
1523
+ /**
1524
+ * Pulls out the options hash from $array if any.
1525
+ *
1526
+ * @internal DO NOT remove the reference on $array.
1527
+ * @param array &$array An array
1528
+ * @return array A valid options array
1529
+ */
1530
+ public static function extract_and_validate_options(array &$array)
1531
+ {
1532
+ $options = array();
1533
+
1534
+ if ($array)
1535
+ {
1536
+ $last = &$array[count($array)-1];
1537
+
1538
+ try
1539
+ {
1540
+ if (self::is_options_hash($last))
1541
+ {
1542
+ array_pop($array);
1543
+ $options = $last;
1544
+ }
1545
+ }
1546
+ catch (ActiveRecordException $e)
1547
+ {
1548
+ if (!is_hash($last))
1549
+ throw $e;
1550
+
1551
+ $options = array('conditions' => $last);
1552
+ }
1553
+ }
1554
+ return $options;
1555
+ }
1556
+
1557
+ /**
1558
+ * Returns a JSON representation of this model.
1559
+ *
1560
+ * @see Serialization
1561
+ * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
1562
+ * @return string JSON representation of the model
1563
+ */
1564
+ public function to_json(array $options=array())
1565
+ {
1566
+ return $this->serialize('Json', $options);
1567
+ }
1568
+
1569
+ /**
1570
+ * Returns an XML representation of this model.
1571
+ *
1572
+ * @see Serialization
1573
+ * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)
1574
+ * @return string XML representation of the model
1575
+ */
1576
+ public function to_xml(array $options=array())
1577
+ {
1578
+ return $this->serialize('Xml', $options);
1579
+ }
1580
+
1581
+ /**
1582
+ * Creates a serializer based on pre-defined to_serializer()
1583
+ *
1584
+ * An options array can take the following parameters:
1585
+ *
1586
+ * <ul>
1587
+ * <li><b>only:</b> a string or array of attributes to be included.</li>
1588
+ * <li><b>excluded:</b> a string or array of attributes to be excluded.</li>
1589
+ * <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
1590
+ * along with the method's returned value</li>
1591
+ * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
1592
+ * </ul>
1593
+ *
1594
+ * @param string $type Either Xml or Json
1595
+ * @param array $options Options array for the serializer
1596
+ * @return string Serialized representation of the model
1597
+ */
1598
+ private function serialize($type, $options)
1599
+ {
1600
+ require_once 'Serialization.php';
1601
+ $class = "ActiveRecord\\{$type}Serializer";
1602
+ $serializer = new $class($this, $options);
1603
+ return $serializer->to_s();
1604
+ }
1605
+
1606
+ /**
1607
+ * Invokes the specified callback on this model.
1608
+ *
1609
+ * @param string $method_name Name of the call back to run.
1610
+ * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
1611
+ * @return boolean True if invoked or null if not
1612
+ */
1613
+ private function invoke_callback($method_name, $must_exist=true)
1614
+ {
1615
+ return static::table()->callback->invoke($this,$method_name,$must_exist);
1616
+ }
1617
+
1618
+ /**
1619
+ * Executes a block of code inside a database transaction.
1620
+ *
1621
+ * <code>
1622
+ * YourModel::transaction(function()
1623
+ * {
1624
+ * YourModel::create(array("name" => "blah"));
1625
+ * });
1626
+ * </code>
1627
+ *
1628
+ * If an exception is thrown inside the closure the transaction will
1629
+ * automatically be rolled back. You can also return false from your
1630
+ * closure to cause a rollback:
1631
+ *
1632
+ * <code>
1633
+ * YourModel::transaction(function()
1634
+ * {
1635
+ * YourModel::create(array("name" => "blah"));
1636
+ * throw new Exception("rollback!");
1637
+ * });
1638
+ *
1639
+ * YourModel::transaction(function()
1640
+ * {
1641
+ * YourModel::create(array("name" => "blah"));
1642
+ * return false; # rollback!
1643
+ * });
1644
+ * </code>
1645
+ *
1646
+ * @param Closure $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.
1647
+ * @return boolean True if the transaction was committed, False if rolled back.
1648
+ */
1649
+ public static function transaction($closure)
1650
+ {
1651
+ $connection = static::connection();
1652
+
1653
+ try
1654
+ {
1655
+ $connection->transaction();
1656
+
1657
+ if ($closure() === false)
1658
+ {
1659
+ $connection->rollback();
1660
+ return false;
1661
+ }
1662
+ else
1663
+ $connection->commit();
1664
+ }
1665
+ catch (\Exception $e)
1666
+ {
1667
+ $connection->rollback();
1668
+ throw $e;
1669
+ }
1670
+ return true;
1671
+ }
1672
+ };
1673
+ ?>