Permalink
Browse files

bam

  • Loading branch information...
0 parents commit 20523eefb5674858b79adaafff283ddf3d77f4d9 @kla kla committed May 22, 2009
Showing with 9,425 additions and 0 deletions.
  1. +39 −0 ActiveRecord.php
  2. +23 −0 LICENSE
  3. +120 −0 README
  4. +15 −0 TODO
  5. +37 −0 examples/orders/models/Order.php
  6. +9 −0 examples/orders/models/Payment.php
  7. +13 −0 examples/orders/models/Person.php
  8. +79 −0 examples/orders/orders.php
  9. +29 −0 examples/orders/orders.sql
  10. +17 −0 examples/simple/simple.php
  11. +7 −0 examples/simple/simple.sql
  12. +32 −0 examples/simple/simple_with_options.php
  13. +6 −0 examples/simple/simple_with_options.sql
  14. +184 −0 lib/CallBack.php
  15. +134 −0 lib/Column.php
  16. +142 −0 lib/Config.php
  17. +198 −0 lib/Connection.php
  18. +38 −0 lib/ConnectionManager.php
  19. +61 −0 lib/Exceptions.php
  20. +189 −0 lib/Expressions.php
  21. +90 −0 lib/Inflector.php
  22. +955 −0 lib/Model.php
  23. +69 −0 lib/Reflections.php
  24. +309 −0 lib/Relationship.php
  25. +276 −0 lib/SQLBuilder.php
  26. +182 −0 lib/Serialization.php
  27. +57 −0 lib/Singleton.php
  28. +303 −0 lib/Table.php
  29. +477 −0 lib/URL.php
  30. +318 −0 lib/Utils.php
  31. +506 −0 lib/Validations.php
  32. +79 −0 lib/adapters/AbstractMysqlAdapter.php
  33. +71 −0 lib/adapters/MysqlAdapter.php
  34. +144 −0 lib/adapters/MysqliAdapter.php
  35. +161 −0 lib/adapters/PgsqlAdapter.php
  36. +166 −0 lib/adapters/Sqlite3Adapter.php
  37. +326 −0 test/ActiveRecordFindTest.php
  38. +222 −0 test/ActiveRecordTest.php
  39. +208 −0 test/ActiveRecordWriteTest.php
  40. +25 −0 test/AllTests.php
  41. +20 −0 test/AllValidationsTest.php
  42. +237 −0 test/CallbackTest.php
  43. +97 −0 test/ColumnTest.php
  44. +80 −0 test/ConfigTest.php
  45. +28 −0 test/ConnectionManagerTest.php
  46. +36 −0 test/ConnectionTest.php
  47. +193 −0 test/ExpressionsTest.php
  48. +17 −0 test/InflectorTest.php
  49. +12 −0 test/MysqlAdapterTest.php
  50. +15 −0 test/MysqliAdapterTest.php
  51. +362 −0 test/RelationshipTest.php
  52. +209 −0 test/SQLBuilderTest.php
  53. +101 −0 test/SerializationTest.php
  54. +37 −0 test/Sqlite3AdapterTest.php
  55. +41 −0 test/UtilsTest.php
  56. +112 −0 test/ValidatesFormatOfTest.php
  57. +158 −0 test/ValidatesInclusionAndExclusionOfTest.php
  58. +241 −0 test/ValidatesLengthOfTest.php
  59. +148 −0 test/ValidatesNumericalityOfTest.php
  60. +57 −0 test/ValidatesPresenceOfTest.php
  61. +32 −0 test/fixtures/data.sql
  62. +69 −0 test/fixtures/mysql.sql
  63. +72 −0 test/fixtures/pgsql.sql
  64. +69 −0 test/fixtures/sqlite3.sql
  65. +265 −0 test/helpers/AdapterTest.php
  66. +93 −0 test/helpers/DatabaseTest.php
  67. +23 −0 test/helpers/config.php
  68. +6 −0 test/models/Author.php
  69. +16 −0 test/models/Book.php
  70. +10 −0 test/models/BookAttrAccessible.php
  71. +6 −0 test/models/Employee.php
  72. +6 −0 test/models/Event.php
  73. +6 −0 test/models/Host.php
  74. +7 −0 test/models/JoinAuthor.php
  75. +6 −0 test/models/JoinBook.php
  76. +7 −0 test/models/NamespaceTest/SomeModel.php
  77. +6 −0 test/models/Position.php
  78. +33 −0 test/models/RmBldg.php
  79. +12 −0 test/models/Venue.php
  80. +109 −0 test/models/VenueCB.php
  81. +55 −0 test/models/VenueGenericCallBacks.php
@@ -0,0 +1,39 @@
+<?
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
+ die('PHP ActiveRecord requires PHP 5.3 or higher');
+
+require_once 'lib/Singleton.php';
+require_once 'lib/Config.php';
+require_once 'lib/Model.php';
+require_once 'lib/Utils.php';
+require_once 'lib/Exceptions.php';
+require_once 'lib/ConnectionManager.php';
+require_once 'lib/Connection.php';
+require_once 'lib/SQLBuilder.php';
+require_once 'lib/Table.php';
+require_once 'lib/Inflector.php';
+require_once 'lib/Validations.php';
+require_once 'lib/Serialization.php';
+require_once 'lib/Reflections.php';
+require_once 'lib/CallBack.php';
+
+spl_autoload_register('activerecord_autoload');
+
+function activerecord_autoload($class_name)
+{
+ $path = ActiveRecord\Config::instance()->get_model_directory();
+ $root = realpath(isset($path) ? $path : '.');
+
+ if (($namespaces = ActiveRecord\get_namespaces($class_name)))
+ {
+ $class_name = array_pop($namespaces);
+ $directories = array();
+ foreach ($namespaces as $directory)
+ $directories[] = $directory;
+
+ $root .= DIRECTORY_SEPARATOR .implode($directories, DIRECTORY_SEPARATOR);
+ }
+
+ @include_once $root . "/$class_name.php";
+}
+?>
@@ -0,0 +1,23 @@
+Copyright (c) 2009
+
+AUTHORS:
+Kien La
+Jacques Fuentes
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,120 @@
+# PHP ActiveRecord #
+
+Version 0.9 beta - Sun 17 May 2009
+
+by Kien La and Jacques Fuentes
+<phpactiverecord@gmail.com>
+<http://www.phpactiverecord.org/>
+
+## Introduction ##
+A brief summarization of what ActiveRecord is:
+
+> Active record is an approach to access data in a database. A database table or view is wrapped into a class,
+> thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to
+> the table upon save. Any object loaded gets its information from the database; when an object is updated, the
+> corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for
+> each column in the table or view.
+
+More details can be found [here](http://en.wikipedia.org/wiki/Active_record_pattern).
+
+This implementation is inspired and thus borrows heavily from Ruby on Rails' ActiveRecord.
+We have tried to maintain their conventions while deviating mainly because of convenience or necessity.
+Of course, there are some differences which will be obvious to the user if they are familiar with rails.
+
+## Installation ##
+
+Setup is very easy and straight-forward. There are essentially only two configuration points you must concern yourself with:
+
+1. Setting the model auto_load directory.
+2. Configuring your database connections.
+
+Example:
+
+ ActiveRecord\Config::initialize(function($cfg)
+ {
+ $cfg->set_model_directory('/path/to/your/model_directory');
+ $cfg->set_connections(array('development' => 'mysql://username:password@localhost/database_name'));
+ });
+
+Alternatively (w/o the 5.3 closure):
+
+ $cfg = ActiveRecord\Config::instance();
+ $cfg->set_model_directory('/path/to/your/model_directory');
+ $cfg->set_connections(array('development' => 'mysql://username:password@localhost/database_name'));
+
+Once you have configured these two settings you are done. ActiveRecord takes care of the rest for you.
+It does not require that you map your table schema to yaml/xml files. It will query the database for this information and
+cache it so that it does not make multiple calls to the database for a single schema.
+
+## Features ##
+
+- Finder methods
+- Dynamic finder methods
+- Writer methods
+- Relationships
+- Validations
+- Callbacks
+- Serializations (json/xml)
+- Support for multiple adapters
+- Miscellaneous options such as: aliased/protected/accessible attributes
+
+Here are some other features we hope to include in later versions:
+- Transactions
+- Named scopes
+- More adapters
+- Relationship includes
+
+## Basic CRUD ##
+
+### Retrieve ###
+These are your basic methods to find and retrieve records from your database.
+See the *Finders* section for more details.
+
+ $post = Post::find(1);
+ echo $post->title; # 'My first blog post!!'
+ echo $post->author_id; # 5
+
+ # also the same since it is the first record in the db
+ $post = Post::first();
+
+ # finding using dynamic finders
+ $post = Post::find_by_name('The Decider');
+ $post = Post::find_by_name_and_id('The Bridge Builder',100);
+ $post = Post::find_by_name_or_id('The Bridge Builder',100);
+
+ # finding using a conditions array
+ $posts = Post::find('all',array('conditions' => array('name=? or id > ?','The Bridge Builder',100)));
+
+### Create ###
+Here we create a new post by instantiating a new object and then invoking the save() method.
+
+ $post = new Post();
+ $post->title = 'My first blog post!!';
+ $post->author_id = 5;
+ $post->save();
+ # INSERT INTO `posts` (title,author_id) VALUES('My first blog post!!', 5)
+
+### Update ###
+To update you would just need to find a record first and then change one of its attributes.
+It keeps an array of attributes that are "dirty" (that have been modified) and so our
+sql will only update the fields modified.
+
+ $post = Post::find(1);
+ echo $post->title; # 'My first blog post!!'
+ $post->title = 'Some real title';
+ $post->save();
+ # UPDATE `posts` SET title='Some real title' WHERE id=1
+
+ $post->title = 'New real title';
+ $post->author_id = 1;
+ $post->save();
+ # UPDATE `posts` SET title='New real title', author_id=1 WHERE id=1
+
+### Delete ###
+Deleting a record will not *destroy* the object. This means that it will call sql to delete
+the record in your database but you can still use the object if you need to.
+
+ $post = Post::find(1);
+ $post->delete();
+ # DELETE FROM `posts` WHERE id=1
+ echo $post->title; # 'New real title'
@@ -0,0 +1,15 @@
+== General
+
+- named scopes
+- add has_to_and_belongs_to_many
+- oracle adapter
+- mssql adapter
+- pgsql adapter
+- using create_xxx, build_xxx on a has_many :thru needs to create both records
+- support create! and save! functionality
+
+== Testing
+
+- make tests run faster by not re-creating whole db for every individual test
+- tests/fixes for funky table/field names
+- more tests for to_xml
@@ -0,0 +1,37 @@
+<?
+class Order extends ActiveRecord\Model
+{
+ // order belongs to a person
+ static $belongs_to = array(
+ array('person'));
+
+ // order can have many payments by many people
+ // the conditions is just there as an example as it makes no logical sense
+ static $has_many = array(
+ array('payments'),
+ array('people',
+ 'through' => 'payments',
+ 'select' => 'people.*, payments.amount',
+ 'conditions' => 'payments.amount < 200'));
+
+ // order must have a price and tax > 0
+ static $validates_numericality_of = array(
+ array('price', 'greater_than' => 0),
+ array('tax', 'greater_than' => 0));
+
+ // setup a callback to automatically apply a tax
+ static $before_validation_on_create = array('apply_tax');
+
+ public function apply_tax()
+ {
+ if ($this->person->state == 'VA')
+ $tax = 0.045;
+ elseif ($this->person->state == 'CA')
+ $tax = 0.10;
+ else
+ $tax = 0.02;
+
+ $this->tax = $this->price * $tax;
+ }
+}
+?>
@@ -0,0 +1,9 @@
+<?
+class Payment extends ActiveRecord\Model
+{
+ // payment belongs to a person
+ static $belongs_to = array(
+ array('person'),
+ array('order'));
+}
+?>
@@ -0,0 +1,13 @@
+<?
+class Person extends ActiveRecord\Model
+{
+ // a person can have many orders and payments
+ static $has_many = array(
+ array('orders'),
+ array('payments'));
+
+ // must have a name and a state
+ static $validates_presence_of = array(
+ array('name'), array('state'));
+}
+?>
@@ -0,0 +1,79 @@
+<?
+require_once dirname(__FILE__) . '/../../ActiveRecord.php';
+
+// initialize ActiveRecord
+ActiveRecord\Config::initialize(function($cfg)
+{
+ $cfg->set_model_directory(dirname(__FILE__) . '/models');
+ $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/orders_test'));
+
+ // you can change the default connection with the below
+ //$cfg->set_default_connection('production');
+});
+
+// create some people
+$jax = new Person(array('name' => 'Jax', 'state' => 'CA'));
+$jax->save();
+
+// compact way to create and save a model
+$tito = Person::create(array('name' => 'Tito', 'state' => 'VA'));
+
+// place orders. tax is automatically applied in a callback
+// create_orders will automatically place the created model into $tito->orders
+// even if it failed validation
+$pokemon = $tito->create_orders(array('item_name' => 'Live Pokemon', 'price' => 6999.99));
+$coal = $tito->create_orders(array('item_name' => 'Lump of Coal', 'price' => 100.00));
+$freebie = $tito->create_orders(array('item_name' => 'Freebie', 'price' => -100.99));
+
+if (count($freebie->errors) > 0)
+ echo "[FAILED] saving order $freebie->item_name: " . join(', ',$freebie->errors->full_messages()) . "\n\n";
+
+// payments
+$pokemon->create_payments(array('amount' => 1.99, 'person_id' => $tito->id));
+$pokemon->create_payments(array('amount' => 4999.50, 'person_id' => $tito->id));
+$pokemon->create_payments(array('amount' => 2.50, 'person_id' => $jax->id));
+
+// reload since we don't want the freebie to show up (because it failed validation)
+$tito->reload();
+
+echo "$tito->name has " . count($tito->orders) . " orders for: " . join(', ',ActiveRecord\collect($tito->orders,'item_name')) . "\n\n";
+
+// get all orders placed by Tito
+foreach (Order::find_all_by_person_id($tito->id) as $order)
+{
+ echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax) ordered by " . $order->person->name . "\n";
+
+ if (count($order->payments) > 0)
+ {
+ // display each payment for this order
+ foreach ($order->payments as $payment)
+ echo " payment #$payment->id of $$payment->amount by " . $payment->person->name . "\n";
+ }
+ else
+ echo " no payments\n";
+
+ echo "\n";
+}
+
+// display summary of all payments made by Tito and Jax
+$conditions = array(
+ 'conditions' => array('id IN(?)',array($tito->id,$jax->id)),
+ 'order' => 'name desc');
+
+foreach (Person::all($conditions) as $person)
+{
+ $n = count($person->payments);
+ $total = array_sum(ActiveRecord\collect($person->payments,'amount'));
+ echo "$person->name made $n payments for a total of $$total\n\n";
+}
+
+// using order has_many people through payments with options
+// array('people', 'through' => 'payments', 'select' => 'people.*, payments.amount', 'conditions' => 'payments.amount < 200'));
+// this means our people in the loop below also has the payment information since it is part of an inner join
+// we will only see 2 of the people instead of 3 because 1 of the payments is greater than 200
+$order = Order::find($pokemon->id);
+echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax)\n";
+
+foreach ($order->people as $person)
+ echo " payment of $$person->amount by " . $person->name . "\n";
+?>
@@ -0,0 +1,29 @@
+-- written for mysql, not tested with any other db
+
+drop table if exists people;
+create table people(
+ id int not null primary key auto_increment,
+ name varchar(50),
+ state char(2),
+ created_at datetime,
+ updated_at datetime
+);
+
+drop table if exists orders;
+create table orders(
+ id int not null primary key auto_increment,
+ person_id int not null,
+ item_name varchar(50),
+ price decimal(10,2),
+ tax decimal(10,2),
+ created_at datetime
+);
+
+drop table if exists payments;
+create table payments(
+ id int not null primary key auto_increment,
+ order_id int not null,
+ person_id int not null,
+ amount decimal(10,2),
+ created_at datetime
+);
@@ -0,0 +1,17 @@
+<?
+require_once dirname(__FILE__) . '/../../ActiveRecord.php';
+
+// assumes a table named "books" with a pk named "id"
+// see simple.sql
+class Book extends ActiveRecord\Model { }
+
+// initialize ActiveRecord
+// change the connection settings to whatever is appropriate for your mysql server
+ActiveRecord\Config::initialize(function($cfg)
+{
+ $cfg->set_model_directory('.');
+ $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/test'));
+});
+
+print_r(Book::first()->attributes());
+?>
@@ -0,0 +1,7 @@
+create table books(
+ id int not null primary key auto_increment,
+ name varchar(50),
+ author varchar(50)
+);
+
+insert into books(name,author) values('How to be Angry','Jax');
Oops, something went wrong.

0 comments on commit 20523ee

Please sign in to comment.