Skip to content
Permalink
Browse files

bam

  • Loading branch information
kla committed May 22, 2009
0 parents commit 20523eefb5674858b79adaafff283ddf3d77f4d9
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";
}
?>
23 LICENSE
@@ -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.
120 README
@@ -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'
15 TODO
@@ -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');

0 comments on commit 20523ee

Please sign in to comment.
You can’t perform that action at this time.