Showing preview only (471K chars total). Download the full file or copy to clipboard to get everything.
Repository: kla/php-activerecord
Branch: master
Commit: d9a4130adbef
Files: 126
Total size: 439.1 KB
Directory structure:
gitextract_w5_8ak3n/
├── .editorconfig
├── .gitignore
├── .travis.yml
├── ActiveRecord.php
├── CHANGELOG
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── examples/
│ ├── orders/
│ │ ├── models/
│ │ │ ├── Order.php
│ │ │ ├── Payment.php
│ │ │ └── Person.php
│ │ ├── orders.php
│ │ └── orders.sql
│ └── simple/
│ ├── simple.php
│ ├── simple.sql
│ ├── simple_with_options.php
│ └── simple_with_options.sql
├── lib/
│ ├── Cache.php
│ ├── CallBack.php
│ ├── Column.php
│ ├── Config.php
│ ├── Connection.php
│ ├── ConnectionManager.php
│ ├── DateTime.php
│ ├── DateTimeInterface.php
│ ├── Exceptions.php
│ ├── Expressions.php
│ ├── Inflector.php
│ ├── Model.php
│ ├── Reflections.php
│ ├── Relationship.php
│ ├── SQLBuilder.php
│ ├── Serialization.php
│ ├── Singleton.php
│ ├── Table.php
│ ├── Utils.php
│ ├── Validations.php
│ ├── adapters/
│ │ ├── MysqlAdapter.php
│ │ ├── OciAdapter.php
│ │ ├── PgsqlAdapter.php
│ │ └── SqliteAdapter.php
│ └── cache/
│ └── Memcache.php
├── phpunit.xml.dist
└── test/
├── ActiveRecordCacheTest.php
├── ActiveRecordFindTest.php
├── ActiveRecordTest.php
├── ActiveRecordWriteTest.php
├── CacheModelTest.php
├── CacheTest.php
├── CallbackTest.php
├── ColumnTest.php
├── ConfigTest.php
├── ConnectionManagerTest.php
├── ConnectionTest.php
├── DateFormatTest.php
├── DateTimeTest.php
├── ExpressionsTest.php
├── HasManyThroughTest.php
├── InflectorTest.php
├── ModelCallbackTest.php
├── MysqlAdapterTest.php
├── OciAdapterTest.php
├── PgsqlAdapterTest.php
├── RelationshipTest.php
├── SQLBuilderTest.php
├── SerializationTest.php
├── SqliteAdapterTest.php
├── UtilsTest.php
├── ValidatesFormatOfTest.php
├── ValidatesInclusionAndExclusionOfTest.php
├── ValidatesLengthOfTest.php
├── ValidatesNumericalityOfTest.php
├── ValidatesPresenceOfTest.php
├── ValidationsTest.php
├── fixtures/
│ ├── amenities.csv
│ ├── authors.csv
│ ├── awesome_people.csv
│ ├── books.csv
│ ├── employees.csv
│ ├── events.csv
│ ├── hosts.csv
│ ├── newsletters.csv
│ ├── positions.csv
│ ├── property.csv
│ ├── property_amenities.csv
│ ├── publishers.csv
│ ├── rm-bldg.csv
│ ├── user_newsletters.csv
│ ├── users.csv
│ ├── valuestore.csv
│ └── venues.csv
├── helpers/
│ ├── AdapterTest.php
│ ├── DatabaseLoader.php
│ ├── DatabaseTest.php
│ ├── SnakeCase_PHPUnit_Framework_TestCase.php
│ ├── config.php
│ └── foo.php
├── models/
│ ├── Amenity.php
│ ├── Author.php
│ ├── AuthorAttrAccessible.php
│ ├── AwesomePerson.php
│ ├── Book.php
│ ├── BookAttrAccessible.php
│ ├── BookAttrProtected.php
│ ├── Employee.php
│ ├── Event.php
│ ├── Host.php
│ ├── JoinAuthor.php
│ ├── JoinBook.php
│ ├── NamespaceTest/
│ │ ├── Book.php
│ │ └── SubNamespaceTest/
│ │ └── Page.php
│ ├── Position.php
│ ├── Property.php
│ ├── PropertyAmenity.php
│ ├── Publisher.php
│ ├── RmBldg.php
│ ├── Venue.php
│ ├── VenueAfterCreate.php
│ └── VenueCB.php
└── sql/
├── mysql.sql
├── oci-after-fixtures.sql
├── oci.sql
├── pgsql-after-fixtures.sql
├── pgsql.sql
└── sqlite.sql
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.php]
indent_style = tab
indent_size = 4
[*.{xml,xml.dist}]
indent_size = 4
================================================
FILE: .gitignore
================================================
.project
.buildpath
.settings
*.log
*.db
*.swp
vendor/*
composer.lock
phpunit.xml
================================================
FILE: .travis.yml
================================================
install: composer install --prefer-source --dev
services:
- memcache
env: PHPAR_MYSQL=mysql://root@127.0.0.1/phpar_test PHPAR_PGSQL=pgsql://postgres@127.0.0.1/phpar_test
language: php
php:
- 5.3
- 5.4
- 5.5
- 7.0
- 7.1
- nightly
matrix:
include:
- php: hhvm
dist: trusty
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true
before_script:
- |
if [[ "${TRAVIS_PHP_VERSION:0:1}" == "7" ]]; then
curl -L https://github.com/websupport-sk/pecl-memcache/archive/NON_BLOCKING_IO_php7.tar.gz | tar xz;
(cd pecl-memcache-NON_BLOCKING_IO_php7 && phpize && ./configure && make && make install);
fi
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo 'extension = "memcache.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi
- mysql -e 'CREATE DATABASE phpar_test;'
- psql -c 'CREATE DATABASE phpar_test;' -U postgres
script: ./vendor/bin/phpunit
================================================
FILE: ActiveRecord.php
================================================
<?php
if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
die('PHP ActiveRecord requires PHP 5.3 or higher');
define('PHP_ACTIVERECORD_VERSION_ID','1.0');
if (!defined('PHP_ACTIVERECORD_AUTOLOAD_PREPEND'))
define('PHP_ACTIVERECORD_AUTOLOAD_PREPEND',true);
require __DIR__.'/lib/Singleton.php';
require __DIR__.'/lib/Config.php';
require __DIR__.'/lib/Utils.php';
require __DIR__.'/lib/DateTimeInterface.php';
require __DIR__.'/lib/DateTime.php';
require __DIR__.'/lib/Model.php';
require __DIR__.'/lib/Table.php';
require __DIR__.'/lib/ConnectionManager.php';
require __DIR__.'/lib/Connection.php';
require __DIR__.'/lib/Serialization.php';
require __DIR__.'/lib/SQLBuilder.php';
require __DIR__.'/lib/Reflections.php';
require __DIR__.'/lib/Inflector.php';
require __DIR__.'/lib/CallBack.php';
require __DIR__.'/lib/Exceptions.php';
require __DIR__.'/lib/Cache.php';
if (!defined('PHP_ACTIVERECORD_AUTOLOAD_DISABLE'))
spl_autoload_register('activerecord_autoload',false,PHP_ACTIVERECORD_AUTOLOAD_PREPEND);
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);
}
$file = "$root/$class_name.php";
if (file_exists($file))
require_once $file;
}
================================================
FILE: CHANGELOG
================================================
Version 1.0 - June 27, 2010
- d2bed65 fixed an error with eager loading when no records exist
- c225942 fixed set methods on DateTime objects to properly flag attributes as dirty
- 46a1219 fixed a memory leak when using validations
- c225942 fixed problem with some model functionality not working correctly after being deserialized
- 3e26749 fixed validates_numericality_of to not ignore other options when only_integer is present and matches
- 53ad5ec fixed ambiguous id error when finding by pk with a join option
- 26e40f4 fixed conditions to accept DateTime values
- 41e52fe changed serialization to serialize datetime fields as strings instead of the actual DateTime objects
- dbee94b Model::transaction() now returns true if commit was successful otherwise false
Versio 1.0 RC1 - May 7, 2010
- support for Oracle
- support for PostgreSQL
- added delegators
- added setters
- added getters
- added HAVING as a finder option
- added ability to find using a hash
- added find_or_create_by
- added validates_uniqueness_of
- added dynamic count_by
- added eager loading
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to PHP ActiveRecord #
We always appreciate contributions to PHP ActiveRecord, but we are not always able to respond as quickly as we would like.
Please do not take delays personal and feel free to remind us by commenting on issues.
### Testing ###
PHP ActiveRecord has a full set of unit tests, which are run by PHPUnit.
In order to run these unit tests, you need to install the required packages using [Composer](https://getcomposer.org/):
```sh
composer install
```
After that you can run the tests by invoking the local PHPUnit
To run all test simply use:
```sh
vendor/bin/phpunit
```
Or run a single test file by specifying its path:
```sh
vendor/bin/phpunit test/InflectorTest.php
```
#### Skipped Tests ####
You might notice that some tests are marked as skipped. To obtain more information about skipped
tests, pass the `--verbose` flag to PHPUnit:
```sh
vendor/bin/phpunit --verbose
```
Some common steps for fixing skipped tests are to:
* Install `memcached` and the PHP memcached extension (e.g., `brew install php56-memcache memcached` on macOS)
* Install the PDO drivers for PostgreSQL (e.g., `brew install php56-pdo-pgsql` on macOS)
* Create a MySQL database and a PostgreSQL database. You can either create these such that they are available at the default locations of `mysql://test:test@127.0.0.1/test` and `pgsql://test:test@127.0.0.1/test` respectively. Alternatively, you can set the `PHPAR_MYSQL` and `PHPAR_PGSQL` environment variables to specify a different location for the MySQL and PostgreSQL databases.
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# PHP ActiveRecord - Version 1.0 #
[](https://travis-ci.org/jpfuentes2/php-activerecord)
by
* [@kla](https://github.com/kla) - Kien La
* [@jpfuentes2](https://github.com/jpfuentes2) - Jacques Fuentes
* [And these terrific Contributors](https://github.com/kla/php-activerecord/contributors)
<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.
## Minimum Requirements ##
- PHP 5.3+
- PDO driver for your respective database
## Supported Databases ##
- MySQL
- SQLite
- PostgreSQL
- Oracle
## Features ##
- Finder methods
- Dynamic finder methods
- Writer methods
- Relationships
- Validations
- Callbacks
- Serializations (json/xml)
- Transactions
- Support for multiple adapters
- Miscellaneous options such as: aliased/protected/accessible attributes
## Installation ##
Setup is very easy and straight-forward. There are essentially only three configuration points you must concern yourself with:
1. Setting the model autoload directory.
2. Configuring your database connections.
3. Setting the database connection to use for your environment.
Example:
```php
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory('/path/to/your/model_directory');
$cfg->set_connections(
array(
'development' => 'mysql://username:password@localhost/development_database_name',
'test' => 'mysql://username:password@localhost/test_database_name',
'production' => 'mysql://username:password@localhost/production_database_name'
)
);
});
```
Alternatively (w/o the 5.3 closure):
```php
$cfg = ActiveRecord\Config::instance();
$cfg->set_model_directory('/path/to/your/model_directory');
$cfg->set_connections(
array(
'development' => 'mysql://username:password@localhost/development_database_name',
'test' => 'mysql://username:password@localhost/test_database_name',
'production' => 'mysql://username:password@localhost/production_database_name'
)
);
```
PHP ActiveRecord will default to use your development database. For testing or production, you simply set the default
connection according to your current environment ('test' or 'production'):
```php
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_default_connection(your_environment);
});
```
Once you have configured these three 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.
## Basic CRUD ##
### Retrieve ###
These are your basic methods to find and retrieve records from your database.
See the *Finders* section for more details.
```php
$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.
```php
$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.
```php
$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.
```php
$post = Post::find(1);
$post->delete();
# DELETE FROM `posts` WHERE id=1
echo $post->title; # 'New real title'
```
## Contributing ##
Please refer to [CONTRIBUTING.md](https://github.com/jpfuentes2/php-activerecord/blob/master/CONTRIBUTING.md) for information on how to contribute to PHP ActiveRecord.
================================================
FILE: composer.json
================================================
{
"name": "php-activerecord/php-activerecord",
"type": "library",
"description": "php-activerecord is an open source ORM library based on the ActiveRecord pattern.",
"keywords": ["activerecord", "orm"],
"homepage": "http://www.phpactiverecord.org/",
"license": "MIT",
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"pear/pear_exception": "1.0-beta1",
"pear/log": "~1.12"
},
"autoload": {
"files": [ "ActiveRecord.php" ]
}
}
================================================
FILE: examples/orders/models/Order.php
================================================
<?php
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;
}
}
?>
================================================
FILE: examples/orders/models/Payment.php
================================================
<?php
class Payment extends ActiveRecord\Model
{
// payment belongs to a person
static $belongs_to = array(
array('person'),
array('order'));
}
?>
================================================
FILE: examples/orders/models/Person.php
================================================
<?php
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'));
}
?>
================================================
FILE: examples/orders/orders.php
================================================
<?php
require_once __DIR__ . '/../../ActiveRecord.php';
// initialize ActiveRecord
ActiveRecord\Config::initialize(function($cfg)
{
$cfg->set_model_directory(__DIR__ . '/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";
?>
================================================
FILE: examples/orders/orders.sql
================================================
-- 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
);
================================================
FILE: examples/simple/simple.php
================================================
<?php
require_once __DIR__ . '/../../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());
?>
================================================
FILE: examples/simple/simple.sql
================================================
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');
================================================
FILE: examples/simple/simple_with_options.php
================================================
<?php
require_once __DIR__ . '/../../ActiveRecord.php';
class Book extends ActiveRecord\Model
{
// explicit table name since our table is not "books"
static $table_name = 'simple_book';
// explicit pk since our pk is not "id"
static $primary_key = 'book_id';
// explicit connection name since we always want production with this model
static $connection = 'production';
// explicit database name will generate sql like so => db.table_name
static $db = 'test';
}
$connections = array(
'development' => 'mysql://invalid',
'production' => 'mysql://test:test@127.0.0.1/test'
);
// initialize ActiveRecord
ActiveRecord\Config::initialize(function($cfg) use ($connections)
{
$cfg->set_model_directory('.');
$cfg->set_connections($connections);
});
print_r(Book::first()->attributes());
?>
================================================
FILE: examples/simple/simple_with_options.sql
================================================
create table simple_book(
book_id int not null primary key auto_increment,
name varchar(50)
);
insert into simple_book (name) values ('simple w/ options!');
================================================
FILE: lib/Cache.php
================================================
<?php
namespace ActiveRecord;
use Closure;
/**
* Cache::get('the-cache-key', function() {
* # this gets executed when cache is stale
* return "your cacheable datas";
* });
*/
class Cache
{
static $adapter = null;
static $options = array();
/**
* Initializes the cache.
*
* With the $options array it's possible to define:
* - expiration of the key, (time in seconds)
* - a namespace for the key
*
* this last one is useful in the case two applications use
* a shared key/store (for instance a shared Memcached db)
*
* Ex:
* $cfg_ar = ActiveRecord\Config::instance();
* $cfg_ar->set_cache('memcache://localhost:11211',array('namespace' => 'my_cool_app',
* 'expire' => 120
* ));
*
* In the example above all the keys expire after 120 seconds, and the
* all get a postfix 'my_cool_app'.
*
* (Note: expiring needs to be implemented in your cache store.)
*
* @param string $url URL to your cache server
* @param array $options Specify additional options
*/
public static function initialize($url, $options=array())
{
if ($url)
{
$url = parse_url($url);
$file = ucwords(Inflector::instance()->camelize($url['scheme']));
$class = "ActiveRecord\\$file";
require_once __DIR__ . "/cache/$file.php";
static::$adapter = new $class($url);
}
else
static::$adapter = null;
static::$options = array_merge(array('expire' => 30, 'namespace' => ''),$options);
}
public static function flush()
{
if (static::$adapter)
static::$adapter->flush();
}
/**
* Attempt to retrieve a value from cache using a key. If the value is not found, then the closure method
* will be invoked, and the result will be stored in cache using that key.
* @param $key
* @param $closure
* @param $expire in seconds
* @return mixed
*/
public static function get($key, $closure, $expire=null)
{
if (!static::$adapter)
return $closure();
if (is_null($expire))
{
$expire = static::$options['expire'];
}
$key = static::get_namespace() . $key;
if (!($value = static::$adapter->read($key)))
static::$adapter->write($key, ($value = $closure()), $expire);
return $value;
}
public static function set($key, $var, $expire=null)
{
if (!static::$adapter)
return;
if (is_null($expire))
{
$expire = static::$options['expire'];
}
$key = static::get_namespace() . $key;
return static::$adapter->write($key, $var, $expire);
}
public static function delete($key)
{
if (!static::$adapter)
return;
$key = static::get_namespace() . $key;
return static::$adapter->delete($key);
}
private static function get_namespace()
{
return (isset(static::$options['namespace']) && strlen(static::$options['namespace']) > 0) ? (static::$options['namespace'] . "::") : "";
}
}
================================================
FILE: lib/CallBack.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
use Closure;
/**
* Callbacks allow the programmer to hook into the life cycle of a {@link Model}.
*
* You can control the state of your object by declaring certain methods to be
* called before or after methods are invoked on your object inside of ActiveRecord.
*
* Valid callbacks are:
* <ul>
* <li><b>after_construct:</b> called after a model has been constructed</li>
* <li><b>before_save:</b> called before a model is saved</li>
* <li><b>after_save:</b> called after a model is saved</li>
* <li><b>before_create:</b> called before a NEW model is to be inserted into the database</li>
* <li><b>after_create:</b> called after a NEW model has been inserted into the database</li>
* <li><b>before_update:</b> called before an existing model has been saved</li>
* <li><b>after_update:</b> called after an existing model has been saved</li>
* <li><b>before_validation:</b> called before running validators</li>
* <li><b>after_validation:</b> called after running validators</li>
* <li><b>before_validation_on_create:</b> called before validation on a NEW model being inserted</li>
* <li><b>after_validation_on_create:</b> called after validation on a NEW model being inserted</li>
* <li><b>before_validation_on_update:</b> see above except for an existing model being saved</li>
* <li><b>after_validation_on_update:</b> ...</li>
* <li><b>before_destroy:</b> called after a model has been deleted</li>
* <li><b>after_destroy:</b> called after a model has been deleted</li>
* </ul>
*
* This class isn't meant to be used directly. Callbacks are defined on your model like the example below:
*
* <code>
* class Person extends ActiveRecord\Model {
* static $before_save = array('make_name_uppercase');
* static $after_save = array('do_happy_dance');
*
* public function make_name_uppercase() {
* $this->name = strtoupper($this->name);
* }
*
* public function do_happy_dance() {
* happy_dance();
* }
* }
* </code>
*
* Available options for callbacks:
*
* <ul>
* <li><b>prepend:</b> puts the callback at the top of the callback chain instead of the bottom</li>
* </ul>
*
* @package ActiveRecord
* @link http://www.phpactiverecord.org/guides/callbacks
*/
class CallBack
{
/**
* List of available callbacks.
*
* @var array
*/
static protected $VALID_CALLBACKS = array(
'after_construct',
'before_save',
'after_save',
'before_create',
'after_create',
'before_update',
'after_update',
'before_validation',
'after_validation',
'before_validation_on_create',
'after_validation_on_create',
'before_validation_on_update',
'after_validation_on_update',
'before_destroy',
'after_destroy'
);
/**
* Container for reflection class of given model
*
* @var object
*/
private $klass;
/**
* List of public methods of the given model
* @var array
*/
private $publicMethods;
/**
* Holds data for registered callbacks.
*
* @var array
*/
private $registry = array();
/**
* Creates a CallBack.
*
* @param string $model_class_name The name of a {@link Model} class
* @return CallBack
*/
public function __construct($model_class_name)
{
$this->klass = Reflections::instance()->get($model_class_name);
foreach (static::$VALID_CALLBACKS as $name)
{
// look for explicitly defined static callback
if (($definition = $this->klass->getStaticPropertyValue($name,null)))
{
if (!is_array($definition))
$definition = array($definition);
foreach ($definition as $method_name)
$this->register($name,$method_name);
}
// implicit callbacks that don't need to have a static definition
// simply define a method named the same as something in $VALID_CALLBACKS
// and the callback is auto-registered
elseif ($this->klass->hasMethod($name))
$this->register($name,$name);
}
}
/**
* Returns all the callbacks registered for a callback type.
*
* @param $name string Name of a callback (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
* @return array array of callbacks or null if invalid callback name.
*/
public function get_callbacks($name)
{
return isset($this->registry[$name]) ? $this->registry[$name] : null;
}
/**
* Invokes a callback.
*
* @internal This is the only piece of the CallBack class that carries its own logic for the
* model object. For (after|before)_(create|update) callbacks, it will merge with
* a generic 'save' callback which is called first for the lease amount of precision.
*
* @param string $model Model to invoke the callback on.
* @param string $name Name of the callback to invoke
* @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
* @return mixed null if $name was not a valid callback type or false if a method was invoked
* that was for a before_* callback and that method returned false. If this happens, execution
* of any other callbacks after the offending callback will not occur.
*/
public function invoke($model, $name, $must_exist=true)
{
if ($must_exist && !array_key_exists($name, $this->registry))
throw new ActiveRecordException("No callbacks were defined for: $name on " . get_class($model));
// if it doesn't exist it might be a /(after|before)_(create|update)/ so we still need to run the save
// callback
if (!array_key_exists($name, $this->registry))
$registry = array();
else
$registry = $this->registry[$name];
$first = substr($name,0,6);
// starts with /(after|before)_(create|update)/
if (($first == 'after_' || $first == 'before') && (($second = substr($name,7,5)) == 'creat' || $second == 'updat' || $second == 'reate' || $second == 'pdate'))
{
$temporal_save = str_replace(array('create', 'update'), 'save', $name);
if (!isset($this->registry[$temporal_save]))
$this->registry[$temporal_save] = array();
$registry = array_merge($this->registry[$temporal_save], $registry ? $registry : array());
}
if ($registry)
{
foreach ($registry as $method)
{
$ret = ($method instanceof Closure ? $method($model) : $model->$method());
if (false === $ret && $first === 'before')
return false;
}
}
return true;
}
/**
* Register a new callback.
*
* The option array can contain the following parameters:
* <ul>
* <li><b>prepend:</b> Add this callback at the beginning of the existing callbacks (true) or at the end (false, default)</li>
* </ul>
*
* @param string $name Name of callback type (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
* @param mixed $closure_or_method_name Either a closure or the name of a method on the {@link Model}
* @param array $options Options array
* @return void
* @throws ActiveRecordException if invalid callback type or callback method was not found
*/
public function register($name, $closure_or_method_name=null, $options=array())
{
$options = array_merge(array('prepend' => false), $options);
if (!$closure_or_method_name)
$closure_or_method_name = $name;
if (!in_array($name,self::$VALID_CALLBACKS))
throw new ActiveRecordException("Invalid callback: $name");
if (!($closure_or_method_name instanceof Closure))
{
if (!isset($this->publicMethods))
$this->publicMethods = get_class_methods($this->klass->getName());
if (!in_array($closure_or_method_name, $this->publicMethods))
{
if ($this->klass->hasMethod($closure_or_method_name))
{
// Method is private or protected
throw new ActiveRecordException("Callback methods need to be public (or anonymous closures). " .
"Please change the visibility of " . $this->klass->getName() . "->" . $closure_or_method_name . "()");
}
else
{
// i'm a dirty ruby programmer
throw new ActiveRecordException("Unknown method for callback: $name" .
(is_string($closure_or_method_name) ? ": #$closure_or_method_name" : ""));
}
}
}
if (!isset($this->registry[$name]))
$this->registry[$name] = array();
if ($options['prepend'])
array_unshift($this->registry[$name], $closure_or_method_name);
else
$this->registry[$name][] = $closure_or_method_name;
}
}
================================================
FILE: lib/Column.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Class for a table column.
*
* @package ActiveRecord
*/
class Column
{
// types for $type
const STRING = 1;
const INTEGER = 2;
const DECIMAL = 3;
const DATETIME = 4;
const DATE = 5;
const TIME = 6;
/**
* Map a type to an column type.
* @static
* @var array
*/
static $TYPE_MAPPING = array(
'datetime' => self::DATETIME,
'timestamp' => self::DATETIME,
'date' => self::DATE,
'time' => self::TIME,
'tinyint' => self::INTEGER,
'smallint' => self::INTEGER,
'mediumint' => self::INTEGER,
'int' => self::INTEGER,
'bigint' => self::INTEGER,
'float' => self::DECIMAL,
'double' => self::DECIMAL,
'numeric' => self::DECIMAL,
'decimal' => self::DECIMAL,
'dec' => self::DECIMAL);
/**
* The true name of this column.
* @var string
*/
public $name;
/**
* The inflected name of this columns .. hyphens/spaces will be => _.
* @var string
*/
public $inflected_name;
/**
* The type of this column: STRING, INTEGER, ...
* @var integer
*/
public $type;
/**
* The raw database specific type.
* @var string
*/
public $raw_type;
/**
* The maximum length of this column.
* @var int
*/
public $length;
/**
* True if this column allows null.
* @var boolean
*/
public $nullable;
/**
* True if this column is a primary key.
* @var boolean
*/
public $pk;
/**
* The default value of the column.
* @var mixed
*/
public $default;
/**
* True if this column is set to auto_increment.
* @var boolean
*/
public $auto_increment;
/**
* Name of the sequence to use for this column if any.
* @var boolean
*/
public $sequence;
/**
* Cast a value to an integer type safely
*
* This will attempt to cast a value to an integer,
* unless its detected that the casting will cause
* the number to overflow or lose precision, in which
* case the number will be returned as a string, so
* that large integers (BIGINTS, unsigned INTS, etc)
* can still be stored without error
*
* This would ideally be done with bcmath or gmp, but
* requiring a new PHP extension for a bug-fix is a
* little ridiculous
*
* @param mixed $value The value to cast
* @return int|string type-casted value
*/
public static function castIntegerSafely($value)
{
if (is_int($value))
return $value;
// Its just a decimal number
elseif (is_numeric($value) && floor($value) != $value)
return (int) $value;
// If adding 0 to a string causes a float conversion,
// we have a number over PHP_INT_MAX
elseif (is_string($value) && is_float($value + 0))
return (string) $value;
// If a float was passed and its greater than PHP_INT_MAX
// (which could be wrong due to floating point precision)
// We'll also check for equal to (>=) in case the precision
// loss creates an overflow on casting
elseif (is_float($value) && $value >= PHP_INT_MAX)
return number_format($value, 0, '', '');
return (int) $value;
}
/**
* Casts a value to the column's type.
*
* @param mixed $value The value to cast
* @param Connection $connection The Connection this column belongs to
* @return mixed type-casted value
*/
public function cast($value, $connection)
{
if ($value === null)
return null;
switch ($this->type)
{
case self::STRING: return (string)$value;
case self::INTEGER: return static::castIntegerSafely($value);
case self::DECIMAL: return (double)$value;
case self::DATETIME:
case self::DATE:
if (!$value)
return null;
$date_class = Config::instance()->get_date_class();
if ($value instanceof $date_class)
return $value;
if ($value instanceof \DateTime)
return $date_class::createFromFormat(
Connection::DATETIME_TRANSLATE_FORMAT,
$value->format(Connection::DATETIME_TRANSLATE_FORMAT),
$value->getTimezone()
);
return $connection->string_to_datetime($value);
}
return $value;
}
/**
* Sets the $type member variable.
* @return mixed
*/
public function map_raw_type()
{
if ($this->raw_type == 'integer')
$this->raw_type = 'int';
if (array_key_exists($this->raw_type,self::$TYPE_MAPPING))
$this->type = self::$TYPE_MAPPING[$this->raw_type];
else
$this->type = self::STRING;
return $this->type;
}
}
================================================
FILE: lib/Config.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
use Closure;
/**
* Manages configuration options for ActiveRecord.
*
* <code>
* ActiveRecord::initialize(function($cfg) {
* $cfg->set_model_home('models');
* $cfg->set_connections(array(
* 'development' => 'mysql://user:pass@development.com/awesome_development',
* 'production' => 'mysql://user:pass@production.com/awesome_production'));
* });
* </code>
*
* @package ActiveRecord
*/
class Config extends Singleton
{
/**
* Name of the connection to use by default.
*
* <code>
* ActiveRecord\Config::initialize(function($cfg) {
* $cfg->set_model_directory('/your/app/models');
* $cfg->set_connections(array(
* 'development' => 'mysql://user:pass@development.com/awesome_development',
* 'production' => 'mysql://user:pass@production.com/awesome_production'));
* });
* </code>
*
* This is a singleton class so you can retrieve the {@link Singleton} instance by doing:
*
* <code>
* $config = ActiveRecord\Config::instance();
* </code>
*
* @var string
*/
private $default_connection = 'development';
/**
* Contains the list of database connection strings.
*
* @var array
*/
private $connections = array();
/**
* Directory for the auto_loading of model classes.
*
* @see activerecord_autoload
* @var string
*/
private $model_directory;
/**
* Switch for logging.
*
* @var bool
*/
private $logging = false;
/**
* Contains a Logger object that must impelement a log() method.
*
* @var object
*/
private $logger;
/**
* Contains the class name for the Date class to use. Must have a public format() method and a
* public static createFromFormat($format, $time) method
*
* @var string
*/
private $date_class = 'ActiveRecord\\DateTime';
/**
* The format to serialize DateTime values into.
*
* @var string
*/
private $date_format = \DateTime::ISO8601;
/**
* Allows config initialization using a closure.
*
* This method is just syntatic sugar.
*
* <code>
* ActiveRecord\Config::initialize(function($cfg) {
* $cfg->set_model_directory('/path/to/your/model_directory');
* $cfg->set_connections(array(
* 'development' => 'mysql://username:password@127.0.0.1/database_name'));
* });
* </code>
*
* You can also initialize by grabbing the singleton object:
*
* <code>
* $cfg = ActiveRecord\Config::instance();
* $cfg->set_model_directory('/path/to/your/model_directory');
* $cfg->set_connections(array('development' =>
* 'mysql://username:password@localhost/database_name'));
* </code>
*
* @param Closure $initializer A closure
* @return void
*/
public static function initialize(Closure $initializer)
{
$initializer(parent::instance());
}
/**
* Sets the list of database connection strings.
*
* <code>
* $config->set_connections(array(
* 'development' => 'mysql://username:password@127.0.0.1/database_name'));
* </code>
*
* @param array $connections Array of connections
* @param string $default_connection Optionally specify the default_connection
* @return void
* @throws ActiveRecord\ConfigException
*/
public function set_connections($connections, $default_connection=null)
{
if (!is_array($connections))
throw new ConfigException("Connections must be an array");
if ($default_connection)
$this->set_default_connection($default_connection);
$this->connections = $connections;
}
/**
* Returns the connection strings array.
*
* @return array
*/
public function get_connections()
{
return $this->connections;
}
/**
* Returns a connection string if found otherwise null.
*
* @param string $name Name of connection to retrieve
* @return string connection info for specified connection name
*/
public function get_connection($name)
{
if (array_key_exists($name, $this->connections))
return $this->connections[$name];
return null;
}
/**
* Returns the default connection string or null if there is none.
*
* @return string
*/
public function get_default_connection_string()
{
return array_key_exists($this->default_connection,$this->connections) ?
$this->connections[$this->default_connection] : null;
}
/**
* Returns the name of the default connection.
*
* @return string
*/
public function get_default_connection()
{
return $this->default_connection;
}
/**
* Set the name of the default connection.
*
* @param string $name Name of a connection in the connections array
* @return void
*/
public function set_default_connection($name)
{
$this->default_connection = $name;
}
/**
* Sets the directory where models are located.
*
* @param string $dir Directory path containing your models
* @return void
*/
public function set_model_directory($dir)
{
$this->model_directory = $dir;
}
/**
* Returns the model directory.
*
* @return string
* @throws ConfigException if specified directory was not found
*/
public function get_model_directory()
{
if ($this->model_directory && !file_exists($this->model_directory))
throw new ConfigException('Invalid or non-existent directory: '.$this->model_directory);
return $this->model_directory;
}
/**
* Turn on/off logging
*
* @param boolean $bool
* @return void
*/
public function set_logging($bool)
{
$this->logging = (bool)$bool;
}
/**
* Sets the logger object for future SQL logging
*
* @param object $logger
* @return void
* @throws ConfigException if Logger objecct does not implement public log()
*/
public function set_logger($logger)
{
$klass = Reflections::instance()->add($logger)->get($logger);
if (!$klass->getMethod('log') || !$klass->getMethod('log')->isPublic())
throw new ConfigException("Logger object must implement a public log method");
$this->logger = $logger;
}
/**
* Return whether or not logging is on
*
* @return boolean
*/
public function get_logging()
{
return $this->logging;
}
/**
* Returns the logger
*
* @return object
*/
public function get_logger()
{
return $this->logger;
}
public function set_date_class($date_class)
{
try {
$klass = Reflections::instance()->add($date_class)->get($date_class);
} catch (\ReflectionException $e) {
throw new ConfigException("Cannot find date class");
}
if (!$klass->hasMethod('format') || !$klass->getMethod('format')->isPublic())
throw new ConfigException('Given date class must have a "public format($format = null)" method');
if (!$klass->hasMethod('createFromFormat') || !$klass->getMethod('createFromFormat')->isPublic())
throw new ConfigException('Given date class must have a "public static createFromFormat($format, $time)" method');
$this->date_class = $date_class;
}
public function get_date_class()
{
return $this->date_class;
}
/**
* @deprecated
*/
public function get_date_format()
{
trigger_error('Use ActiveRecord\Serialization::$DATETIME_FORMAT. Config::get_date_format() has been deprecated.', E_USER_DEPRECATED);
return Serialization::$DATETIME_FORMAT;
}
/**
* @deprecated
*/
public function set_date_format($format)
{
trigger_error('Use ActiveRecord\Serialization::$DATETIME_FORMAT. Config::set_date_format() has been deprecated.', E_USER_DEPRECATED);
Serialization::$DATETIME_FORMAT = $format;
}
/**
* Sets the url for the cache server to enable query caching.
*
* Only table schema queries are cached at the moment. A general query cache
* will follow.
*
* Example:
*
* <code>
* $config->set_cache("memcached://localhost");
* $config->set_cache("memcached://localhost",array("expire" => 60));
* </code>
*
* @param string $url Url to your cache server.
* @param array $options Array of options
*/
public function set_cache($url, $options=array())
{
Cache::initialize($url,$options);
}
}
================================================
FILE: lib/Connection.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
require_once 'Column.php';
use PDO;
use PDOException;
use Closure;
/**
* The base class for database connection adapters.
*
* @package ActiveRecord
*/
abstract class Connection
{
/**
* The DateTime format to use when translating other DateTime-compatible objects.
*
* NOTE!: The DateTime "format" used must not include a time-zone (name, abbreviation, etc) or offset.
* Including one will cause PHP to ignore the passed in time-zone in the 3rd argument.
* See bug: https://bugs.php.net/bug.php?id=61022
*
* @var string
*/
const DATETIME_TRANSLATE_FORMAT = 'Y-m-d\TH:i:s';
/**
* The PDO connection object.
* @var mixed
*/
public $connection;
/**
* The last query run.
* @var string
*/
public $last_query;
/**
* Switch for logging.
*
* @var bool
*/
private $logging = false;
/**
* Contains a Logger object that must impelement a log() method.
*
* @var object
*/
private $logger;
/**
* The name of the protocol that is used.
* @var string
*/
public $protocol;
/**
* Database's date format
* @var string
*/
static $date_format = 'Y-m-d';
/**
* Database's datetime format
* @var string
*/
static $datetime_format = 'Y-m-d H:i:s T';
/**
* Default PDO options to set for each connection.
* @var array
*/
static $PDO_OPTIONS = array(
PDO::ATTR_CASE => PDO::CASE_LOWER,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false);
/**
* The quote character for stuff like column and field names.
* @var string
*/
static $QUOTE_CHARACTER = '`';
/**
* Default port.
* @var int
*/
static $DEFAULT_PORT = 0;
/**
* Retrieve a database connection.
*
* @param string $connection_string_or_connection_name A database connection string (ex. mysql://user:pass@host[:port]/dbname)
* Everything after the protocol:// part is specific to the connection adapter.
* OR
* A connection name that is set in ActiveRecord\Config
* If null it will use the default connection specified by ActiveRecord\Config->set_default_connection
* @return Connection
* @see parse_connection_url
*/
public static function instance($connection_string_or_connection_name=null)
{
$config = Config::instance();
if (strpos($connection_string_or_connection_name, '://') === false)
{
$connection_string = $connection_string_or_connection_name ?
$config->get_connection($connection_string_or_connection_name) :
$config->get_default_connection_string();
}
else
$connection_string = $connection_string_or_connection_name;
if (!$connection_string)
throw new DatabaseException("Empty connection string");
$info = static::parse_connection_url($connection_string);
$fqclass = static::load_adapter_class($info->protocol);
try {
$connection = new $fqclass($info);
$connection->protocol = $info->protocol;
$connection->logging = $config->get_logging();
$connection->logger = $connection->logging ? $config->get_logger() : null;
if (isset($info->charset))
$connection->set_encoding($info->charset);
} catch (PDOException $e) {
throw new DatabaseException($e);
}
return $connection;
}
/**
* Loads the specified class for an adapter.
*
* @param string $adapter Name of the adapter.
* @return string The full name of the class including namespace.
*/
private static function load_adapter_class($adapter)
{
$class = ucwords($adapter) . 'Adapter';
$fqclass = 'ActiveRecord\\' . $class;
$source = __DIR__ . "/adapters/$class.php";
if (!file_exists($source))
throw new DatabaseException("$fqclass not found!");
require_once($source);
return $fqclass;
}
/**
* Use this for any adapters that can take connection info in the form below
* to set the adapters connection info.
*
* <code>
* protocol://username:password@host[:port]/dbname
* protocol://urlencoded%20username:urlencoded%20password@host[:port]/dbname?decode=true
* protocol://username:password@unix(/some/file/path)/dbname
* </code>
*
* Sqlite has a special syntax, as it does not need a database name or user authentication:
*
* <code>
* sqlite://file.db
* sqlite://../relative/path/to/file.db
* sqlite://unix(/absolute/path/to/file.db)
* sqlite://windows(c%2A/absolute/path/to/file.db)
* </code>
*
* @param string $connection_url A connection URL
* @return object the parsed URL as an object.
*/
public static function parse_connection_url($connection_url)
{
$url = @parse_url($connection_url);
if (!isset($url['host']))
throw new DatabaseException('Database host must be specified in the connection string. If you want to specify an absolute filename, use e.g. sqlite://unix(/path/to/file)');
$info = new \stdClass();
$info->protocol = $url['scheme'];
$info->host = $url['host'];
$info->db = isset($url['path']) ? substr($url['path'], 1) : null;
$info->user = isset($url['user']) ? $url['user'] : null;
$info->pass = isset($url['pass']) ? $url['pass'] : null;
$allow_blank_db = ($info->protocol == 'sqlite');
if ($info->host == 'unix(')
{
$socket_database = $info->host . '/' . $info->db;
if ($allow_blank_db)
$unix_regex = '/^unix\((.+)\)\/?().*$/';
else
$unix_regex = '/^unix\((.+)\)\/(.+)$/';
if (preg_match_all($unix_regex, $socket_database, $matches) > 0)
{
$info->host = $matches[1][0];
$info->db = $matches[2][0];
}
} elseif (substr($info->host, 0, 8) == 'windows(')
{
$info->host = urldecode(substr($info->host, 8) . '/' . substr($info->db, 0, -1));
$info->db = null;
}
if ($allow_blank_db && $info->db)
$info->host .= '/' . $info->db;
if (isset($url['port']))
$info->port = $url['port'];
if (strpos($connection_url, 'decode=true') !== false)
{
if ($info->user)
$info->user = urldecode($info->user);
if ($info->pass)
$info->pass = urldecode($info->pass);
}
if (isset($url['query']))
{
foreach (explode('/&/', $url['query']) as $pair) {
list($name, $value) = explode('=', $pair);
if ($name == 'charset')
$info->charset = $value;
}
}
return $info;
}
/**
* Class Connection is a singleton. Access it via instance().
*
* @param array $info Array containing URL parts
* @return Connection
*/
protected function __construct($info)
{
try {
// unix sockets start with a /
if ($info->host[0] != '/')
{
$host = "host=$info->host";
if (isset($info->port))
$host .= ";port=$info->port";
}
else
$host = "unix_socket=$info->host";
$this->connection = new PDO("$info->protocol:$host;dbname=$info->db", $info->user, $info->pass, static::$PDO_OPTIONS);
} catch (PDOException $e) {
throw new DatabaseException($e);
}
}
/**
* Retrieves column meta data for the specified table.
*
* @param string $table Name of a table
* @return array An array of {@link Column} objects.
*/
public function columns($table)
{
$columns = array();
$sth = $this->query_column_info($table);
while (($row = $sth->fetch())) {
$c = $this->create_column($row);
$columns[$c->name] = $c;
}
return $columns;
}
/**
* Escapes quotes in a string.
*
* @param string $string The string to be quoted.
* @return string The string with any quotes in it properly escaped.
*/
public function escape($string)
{
return $this->connection->quote($string);
}
/**
* Retrieve the insert id of the last model saved.
*
* @param string $sequence Optional name of a sequence to use
* @return int
*/
public function insert_id($sequence=null)
{
return $this->connection->lastInsertId($sequence);
}
/**
* Execute a raw SQL query on the database.
*
* @param string $sql Raw SQL string to execute.
* @param array &$values Optional array of bind values
* @return mixed A result set object
*/
public function query($sql, &$values=array())
{
if ($this->logging)
{
$this->logger->log($sql);
if ( $values ) $this->logger->log($values);
}
$this->last_query = $sql;
try {
if (!($sth = $this->connection->prepare($sql)))
throw new DatabaseException($this);
} catch (PDOException $e) {
throw new DatabaseException($this);
}
$sth->setFetchMode(PDO::FETCH_ASSOC);
try {
if (!$sth->execute($values))
throw new DatabaseException($this);
} catch (PDOException $e) {
throw new DatabaseException($e);
}
return $sth;
}
/**
* Execute a query that returns maximum of one row with one field and return it.
*
* @param string $sql Raw SQL string to execute.
* @param array &$values Optional array of values to bind to the query.
* @return string
*/
public function query_and_fetch_one($sql, &$values=array())
{
$sth = $this->query($sql, $values);
$row = $sth->fetch(PDO::FETCH_NUM);
return $row[0];
}
/**
* Execute a raw SQL query and fetch the results.
*
* @param string $sql Raw SQL string to execute.
* @param Closure $handler Closure that will be passed the fetched results.
*/
public function query_and_fetch($sql, Closure $handler)
{
$sth = $this->query($sql);
while (($row = $sth->fetch(PDO::FETCH_ASSOC)))
$handler($row);
}
/**
* Returns all tables for the current database.
*
* @return array Array containing table names.
*/
public function tables()
{
$tables = array();
$sth = $this->query_for_tables();
while (($row = $sth->fetch(PDO::FETCH_NUM)))
$tables[] = $row[0];
return $tables;
}
/**
* Starts a transaction.
*/
public function transaction()
{
if (!$this->connection->beginTransaction())
throw new DatabaseException($this);
}
/**
* Commits the current transaction.
*/
public function commit()
{
if (!$this->connection->commit())
throw new DatabaseException($this);
}
/**
* Rollback a transaction.
*/
public function rollback()
{
if (!$this->connection->rollback())
throw new DatabaseException($this);
}
/**
* Tells you if this adapter supports sequences or not.
*
* @return boolean
*/
function supports_sequences()
{
return false;
}
/**
* Return a default sequence name for the specified table.
*
* @param string $table Name of a table
* @param string $column_name Name of column sequence is for
* @return string sequence name or null if not supported.
*/
public function get_sequence_name($table, $column_name)
{
return "{$table}_seq";
}
/**
* Return SQL for getting the next value in a sequence.
*
* @param string $sequence_name Name of the sequence
* @return string
*/
public function next_sequence_value($sequence_name)
{
return null;
}
/**
* Quote a name like table names and field names.
*
* @param string $string String to quote.
* @return string
*/
public function quote_name($string)
{
return $string[0] === static::$QUOTE_CHARACTER || $string[strlen($string) - 1] === static::$QUOTE_CHARACTER ?
$string : static::$QUOTE_CHARACTER . $string . static::$QUOTE_CHARACTER;
}
/**
* Return a date time formatted into the database's date format.
*
* @param DateTime $datetime The DateTime object
* @return string
*/
public function date_to_string($datetime)
{
return $datetime->format(static::$date_format);
}
/**
* Return a date time formatted into the database's datetime format.
*
* @param DateTime $datetime The DateTime object
* @return string
*/
public function datetime_to_string($datetime)
{
return $datetime->format(static::$datetime_format);
}
/**
* Converts a string representation of a datetime into a DateTime object.
*
* @param string $string A datetime in the form accepted by date_create()
* @return object The date_class set in Config
*/
public function string_to_datetime($string)
{
$date = date_create($string);
$errors = \DateTime::getLastErrors();
if ($errors['warning_count'] > 0 || $errors['error_count'] > 0)
return null;
$date_class = Config::instance()->get_date_class();
return $date_class::createFromFormat(
static::DATETIME_TRANSLATE_FORMAT,
$date->format(static::DATETIME_TRANSLATE_FORMAT),
$date->getTimezone()
);
}
/**
* Adds a limit clause to the SQL query.
*
* @param string $sql The SQL statement.
* @param int $offset Row offset to start at.
* @param int $limit Maximum number of rows to return.
* @return string The SQL query that will limit results to specified parameters
*/
abstract function limit($sql, $offset, $limit);
/**
* Query for column meta info and return statement handle.
*
* @param string $table Name of a table
* @return PDOStatement
*/
abstract public function query_column_info($table);
/**
* Query for all tables in the current database. The result must only
* contain one column which has the name of the table.
*
* @return PDOStatement
*/
abstract function query_for_tables();
/**
* Executes query to specify the character set for this connection.
*/
abstract function set_encoding($charset);
/*
* Returns an array mapping of native database types
*/
abstract public function native_database_types();
/**
* Specifies whether or not adapter can use LIMIT/ORDER clauses with DELETE & UPDATE operations
*
* @internal
* @returns boolean (FALSE by default)
*/
public function accepts_limit_and_order_for_update_and_delete()
{
return false;
}
}
================================================
FILE: lib/ConnectionManager.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Singleton to manage any and all database connections.
*
* @package ActiveRecord
*/
class ConnectionManager extends Singleton
{
/**
* Array of {@link Connection} objects.
* @var array
*/
static private $connections = array();
/**
* If $name is null then the default connection will be returned.
*
* @see Config
* @param string $name Optional name of a connection
* @return Connection
*/
public static function get_connection($name=null)
{
$config = Config::instance();
$name = $name ? $name : $config->get_default_connection();
if (!isset(self::$connections[$name]) || !self::$connections[$name]->connection)
self::$connections[$name] = Connection::instance($config->get_connection($name));
return self::$connections[$name];
}
/**
* Drops the connection from the connection manager. Does not actually close it since there
* is no close method in PDO.
*
* If $name is null then the default connection will be returned.
*
* @param string $name Name of the connection to forget about
*/
public static function drop_connection($name=null)
{
$config = Config::instance();
$name = $name ? $name : $config->get_default_connection();
if (isset(self::$connections[$name]))
unset(self::$connections[$name]);
}
}
================================================
FILE: lib/DateTime.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* An extension of PHP's DateTime class to provide dirty flagging and easier formatting options.
*
* All date and datetime fields from your database will be created as instances of this class.
*
* Example of formatting and changing the default format:
*
* <code>
* $now = new ActiveRecord\DateTime('2010-01-02 03:04:05');
* ActiveRecord\DateTime::$DEFAULT_FORMAT = 'short';
*
* echo $now->format(); # 02 Jan 03:04
* echo $now->format('atom'); # 2010-01-02T03:04:05-05:00
* echo $now->format('Y-m-d'); # 2010-01-02
*
* # __toString() uses the default formatter
* echo (string)$now; # 02 Jan 03:04
* </code>
*
* You can also add your own pre-defined friendly formatters:
*
* <code>
* ActiveRecord\DateTime::$FORMATS['awesome_format'] = 'H:i:s m/d/Y';
* echo $now->format('awesome_format') # 03:04:05 01/02/2010
* </code>
*
* @package ActiveRecord
* @see http://php.net/manual/en/class.datetime.php
*/
class DateTime extends \DateTime implements DateTimeInterface
{
/**
* Default format used for format() and __toString()
*/
public static $DEFAULT_FORMAT = 'rfc2822';
/**
* Pre-defined format strings.
*/
public static $FORMATS = array(
'db' => 'Y-m-d H:i:s',
'number' => 'YmdHis',
'time' => 'H:i',
'short' => 'd M H:i',
'long' => 'F d, Y H:i',
'atom' => \DateTime::ATOM,
'cookie' => \DateTime::COOKIE,
'iso8601' => \DateTime::ISO8601,
'rfc822' => \DateTime::RFC822,
'rfc850' => \DateTime::RFC850,
'rfc1036' => \DateTime::RFC1036,
'rfc1123' => \DateTime::RFC1123,
'rfc2822' => \DateTime::RFC2822,
'rfc3339' => \DateTime::RFC3339,
'rss' => \DateTime::RSS,
'w3c' => \DateTime::W3C);
private $model;
private $attribute_name;
public function attribute_of($model, $attribute_name)
{
$this->model = $model;
$this->attribute_name = $attribute_name;
}
/**
* Formats the DateTime to the specified format.
*
* <code>
* $datetime->format(); # uses the format defined in DateTime::$DEFAULT_FORMAT
* $datetime->format('short'); # d M H:i
* $datetime->format('Y-m-d'); # Y-m-d
* </code>
*
* @see FORMATS
* @see get_format
* @param string $format A format string accepted by get_format()
* @return string formatted date and time string
*/
public function format($format=null)
{
return parent::format(self::get_format($format));
}
/**
* Returns the format string.
*
* If $format is a pre-defined format in $FORMATS it will return that otherwise
* it will assume $format is a format string itself.
*
* @see FORMATS
* @param string $format A pre-defined string format or a raw format string
* @return string a format string
*/
public static function get_format($format=null)
{
// use default format if no format specified
if (!$format)
$format = self::$DEFAULT_FORMAT;
// format is a friendly
if (array_key_exists($format, self::$FORMATS))
return self::$FORMATS[$format];
// raw format
return $format;
}
/**
* This needs to be overriden so it returns an instance of this class instead of PHP's \DateTime.
* See http://php.net/manual/en/datetime.createfromformat.php
*/
public static function createFromFormat($format, $time, $tz = null)
{
$phpDate = $tz ? parent::createFromFormat($format, $time, $tz) : parent::createFromFormat($format, $time);
if (!$phpDate)
return false;
// convert to this class using the timestamp
$ourDate = new static(null, $phpDate->getTimezone());
$ourDate->setTimestamp($phpDate->getTimestamp());
return $ourDate;
}
public function __toString()
{
return $this->format();
}
/**
* Handle PHP object `clone`.
*
* This makes sure that the object doesn't still flag an attached model as
* dirty after cloning the DateTime object and making modifications to it.
*
* @return void
*/
public function __clone()
{
$this->model = null;
$this->attribute_name = null;
}
private function flag_dirty()
{
if ($this->model)
$this->model->flag_dirty($this->attribute_name);
}
public function setDate($year, $month, $day)
{
$this->flag_dirty();
return parent::setDate($year, $month, $day);
}
public function setISODate($year, $week , $day = 1)
{
$this->flag_dirty();
return parent::setISODate($year, $week, $day);
}
public function setTime($hour, $minute, $second = 0, $microseconds = 0)
{
$this->flag_dirty();
return parent::setTime($hour, $minute, $second);
}
public function setTimestamp($unixtimestamp)
{
$this->flag_dirty();
return parent::setTimestamp($unixtimestamp);
}
public function setTimezone($timezone)
{
$this->flag_dirty();
return parent::setTimezone($timezone);
}
public function modify($modify)
{
$this->flag_dirty();
return parent::modify($modify);
}
public function add($interval)
{
$this->flag_dirty();
return parent::add($interval);
}
public function sub($interval)
{
$this->flag_dirty();
return parent::sub($interval);
}
}
================================================
FILE: lib/DateTimeInterface.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Interface for the ActiveRecord\DateTime class so that ActiveRecord\Model->assign_attribute() will
* know to call attribute_of() on passed values. This is so the DateTime object can flag the model
* as dirty via $model->flag_dirty() when one of its setters is called.
*
* @package ActiveRecord
* @see http://php.net/manual/en/class.datetime.php
*/
interface DateTimeInterface
{
/**
* Indicates this object is an attribute of the specified model, with the given attribute name.
*
* @param Model $model The model this object is an attribute of
* @param string $attribute_name The attribute name
* @return void
*/
public function attribute_of($model, $attribute_name);
/**
* Formats the DateTime to the specified format.
*/
public function format($format=null);
/**
* See http://php.net/manual/en/datetime.createfromformat.php
*/
public static function createFromFormat($format, $time, $tz = null);
}
================================================
FILE: lib/Exceptions.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Generic base exception for all ActiveRecord specific errors.
*
* @package ActiveRecord
*/
class ActiveRecordException extends \Exception {}
/**
* Thrown when a record cannot be found.
*
* @package ActiveRecord
*/
class RecordNotFound extends ActiveRecordException {}
/**
* Thrown when there was an error performing a database operation.
*
* The error will be specific to whatever database you are running.
*
* @package ActiveRecord
*/
class DatabaseException extends ActiveRecordException
{
public function __construct($adapter_or_string_or_mystery)
{
if ($adapter_or_string_or_mystery instanceof Connection)
{
parent::__construct(
join(", ",$adapter_or_string_or_mystery->connection->errorInfo()),
intval($adapter_or_string_or_mystery->connection->errorCode()));
}
elseif ($adapter_or_string_or_mystery instanceof \PDOStatement)
{
parent::__construct(
join(", ",$adapter_or_string_or_mystery->errorInfo()),
intval($adapter_or_string_or_mystery->errorCode()));
}
else
parent::__construct($adapter_or_string_or_mystery);
}
}
/**
* Thrown by {@link Model}.
*
* @package ActiveRecord
*/
class ModelException extends ActiveRecordException {}
/**
* Thrown by {@link Expressions}.
*
* @package ActiveRecord
*/
class ExpressionsException extends ActiveRecordException {}
/**
* Thrown for configuration problems.
*
* @package ActiveRecord
*/
class ConfigException extends ActiveRecordException {}
/**
* Thrown for cache problems.
*
* @package ActiveRecord
*/
class CacheException extends ActiveRecordException {}
/**
* Thrown when attempting to access an invalid property on a {@link Model}.
*
* @package ActiveRecord
*/
class UndefinedPropertyException extends ModelException
{
/**
* Sets the exception message to show the undefined property's name.
*
* @param str $property_name name of undefined property
* @return void
*/
public function __construct($class_name, $property_name)
{
if (is_array($property_name))
{
$this->message = implode("\r\n", $property_name);
return;
}
$this->message = "Undefined property: {$class_name}->{$property_name} in {$this->file} on line {$this->line}";
parent::__construct();
}
}
/**
* Thrown when attempting to perform a write operation on a {@link Model} that is in read-only mode.
*
* @package ActiveRecord
*/
class ReadOnlyException extends ModelException
{
/**
* Sets the exception message to show the undefined property's name.
*
* @param str $class_name name of the model that is read only
* @param str $method_name name of method which attempted to modify the model
* @return void
*/
public function __construct($class_name, $method_name)
{
$this->message = "{$class_name}::{$method_name}() cannot be invoked because this model is set to read only";
parent::__construct();
}
}
/**
* Thrown for validations exceptions.
*
* @package ActiveRecord
*/
class ValidationsArgumentError extends ActiveRecordException {}
/**
* Thrown for relationship exceptions.
*
* @package ActiveRecord
*/
class RelationshipException extends ActiveRecordException {}
/**
* Thrown for has many thru exceptions.
*
* @package ActiveRecord
*/
class HasManyThroughAssociationException extends RelationshipException {}
================================================
FILE: lib/Expressions.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Templating like class for building SQL statements.
*
* Examples:
* 'name = :name AND author = :author'
* 'id = IN(:ids)'
* 'id IN(:subselect)'
*
* @package ActiveRecord
*/
class Expressions
{
const ParameterMarker = '?';
private $expressions;
private $values = array();
private $connection;
public function __construct($connection, $expressions=null /* [, $values ... ] */)
{
$values = null;
$this->connection = $connection;
if (is_array($expressions))
{
$glue = func_num_args() > 2 ? func_get_arg(2) : ' AND ';
list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue);
}
if ($expressions != '')
{
if (!$values)
$values = array_slice(func_get_args(),2);
$this->values = $values;
$this->expressions = $expressions;
}
}
/**
* Bind a value to the specific one based index. There must be a bind marker
* for each value bound or to_s() will throw an exception.
*/
public function bind($parameter_number, $value)
{
if ($parameter_number <= 0)
throw new ExpressionsException("Invalid parameter index: $parameter_number");
$this->values[$parameter_number-1] = $value;
}
public function bind_values($values)
{
$this->values = $values;
}
/**
* Returns all the values currently bound.
*/
public function values()
{
return $this->values;
}
/**
* Returns the connection object.
*/
public function get_connection()
{
return $this->connection;
}
/**
* Sets the connection object. It is highly recommended to set this so we can
* use the adapter's native escaping mechanism.
*
* @param string $connection a Connection instance
*/
public function set_connection($connection)
{
$this->connection = $connection;
}
public function to_s($substitute=false, &$options=null)
{
if (!$options) $options = array();
$values = array_key_exists('values',$options) ? $options['values'] : $this->values;
$ret = "";
$replace = array();
$num_values = count($values);
$len = strlen($this->expressions);
$quotes = 0;
for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i)
{
$ch = $this->expressions[$i];
if ($ch == self::ParameterMarker)
{
if ($quotes % 2 == 0)
{
if ($j > $num_values-1)
throw new ExpressionsException("No bound parameter for index $j");
$ch = $this->substitute($values,$substitute,$i,$j++);
}
}
elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\')
++$quotes;
$ret .= $ch;
}
return $ret;
}
private function build_sql_from_hash(&$hash, $glue)
{
$sql = $g = "";
foreach ($hash as $name => $value)
{
if ($this->connection)
$name = $this->connection->quote_name($name);
if (is_array($value))
$sql .= "$g$name IN(?)";
elseif (is_null($value))
$sql .= "$g$name IS ?";
else
$sql .= "$g$name=?";
$g = $glue;
}
return array($sql,array_values($hash));
}
private function substitute(&$values, $substitute, $pos, $parameter_index)
{
$value = $values[$parameter_index];
if (is_array($value))
{
$value_count = count($value);
if ($value_count === 0)
if ($substitute)
return 'NULL';
else
return self::ParameterMarker;
if ($substitute)
{
$ret = '';
for ($i=0, $n=$value_count; $i<$n; ++$i)
$ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]);
return $ret;
}
return join(',',array_fill(0,$value_count,self::ParameterMarker));
}
if ($substitute)
return $this->stringify_value($value);
return $this->expressions[$pos];
}
private function stringify_value($value)
{
if (is_null($value))
return "NULL";
return is_string($value) ? $this->quote_string($value) : $value;
}
private function quote_string($value)
{
if ($this->connection)
return $this->connection->escape($value);
return "'" . str_replace("'","''",$value) . "'";
}
}
================================================
FILE: lib/Inflector.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* @package ActiveRecord
*/
abstract class Inflector
{
/**
* Get an instance of the {@link Inflector} class.
*
* @return object
*/
public static function instance()
{
return new StandardInflector();
}
/**
* Turn a string into its camelized version.
*
* @param string $s string to convert
* @return string
*/
public function camelize($s)
{
$s = preg_replace('/[_-]+/','_',trim($s));
$s = str_replace(' ', '_', $s);
$camelized = '';
for ($i=0,$n=strlen($s); $i<$n; ++$i)
{
if ($s[$i] == '_' && $i+1 < $n)
$camelized .= strtoupper($s[++$i]);
else
$camelized .= $s[$i];
}
$camelized = trim($camelized,' _');
if (strlen($camelized) > 0)
$camelized[0] = strtolower($camelized[0]);
return $camelized;
}
/**
* Determines if a string contains all uppercase characters.
*
* @param string $s string to check
* @return bool
*/
public static function is_upper($s)
{
return (strtoupper($s) === $s);
}
/**
* Determines if a string contains all lowercase characters.
*
* @param string $s string to check
* @return bool
*/
public static function is_lower($s)
{
return (strtolower($s) === $s);
}
/**
* Convert a camelized string to a lowercase, underscored string.
*
* @param string $s string to convert
* @return string
*/
public function uncamelize($s)
{
$normalized = '';
for ($i=0,$n=strlen($s); $i<$n; ++$i)
{
if (ctype_alpha($s[$i]) && self::is_upper($s[$i]))
$normalized .= '_' . strtolower($s[$i]);
else
$normalized .= $s[$i];
}
return trim($normalized,' _');
}
/**
* Convert a string with space into a underscored equivalent.
*
* @param string $s string to convert
* @return string
*/
public function underscorify($s)
{
return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s));
}
public function keyify($class_name)
{
return strtolower($this->underscorify(denamespace($class_name))) . '_id';
}
abstract function variablize($s);
}
/**
* @package ActiveRecord
*/
class StandardInflector extends Inflector
{
public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); }
public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); }
}
================================================
FILE: lib/Model.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* The base class for your models.
*
* Defining an ActiveRecord model for a table called people and orders:
*
* <code>
* CREATE TABLE people(
* id int primary key auto_increment,
* parent_id int,
* first_name varchar(50),
* last_name varchar(50)
* );
*
* CREATE TABLE orders(
* id int primary key auto_increment,
* person_id int not null,
* cost decimal(10,2),
* total decimal(10,2)
* );
* </code>
*
* <code>
* class Person extends ActiveRecord\Model {
* static $belongs_to = array(
* array('parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person')
* );
*
* static $has_many = array(
* array('children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'),
* array('orders')
* );
*
* static $validates_length_of = array(
* array('first_name', 'within' => array(1,50)),
* array('last_name', 'within' => array(1,50))
* );
* }
*
* class Order extends ActiveRecord\Model {
* static $belongs_to = array(
* array('person')
* );
*
* static $validates_numericality_of = array(
* array('cost', 'greater_than' => 0),
* array('total', 'greater_than' => 0)
* );
*
* static $before_save = array('calculate_total_with_tax');
*
* public function calculate_total_with_tax() {
* $this->total = $this->cost * 0.045;
* }
* }
* </code>
*
* For a more in-depth look at defining models, relationships, callbacks and many other things
* please consult our {@link http://www.phpactiverecord.org/guides Guides}.
*
* @package ActiveRecord
* @see BelongsTo
* @see CallBack
* @see HasMany
* @see HasAndBelongsToMany
* @see Serialization
* @see Validations
*/
class Model
{
/**
* An instance of {@link Errors} and will be instantiated once a write method is called.
*
* @var Errors
*/
public $errors;
/**
* Contains model values as column_name => value
*
* @var array
*/
private $attributes = array();
/**
* 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
*
* @var array
*/
private $__dirty = null;
/**
* Flag that determines of this model can have a writer method invoked such as: save/update/insert/delete
*
* @var boolean
*/
private $__readonly = false;
/**
* Array of relationship objects as model_attribute_name => relationship
*
* @var array
*/
private $__relationships = array();
/**
* Flag that determines if a call to save() should issue an insert or an update sql statement
*
* @var boolean
*/
private $__new_record = true;
/**
* Set to the name of the connection this {@link Model} should use.
*
* @var string
*/
static $connection;
/**
* Set to the name of the database this Model's table is in.
*
* @var string
*/
static $db;
/**
* Set this to explicitly specify the model's table name if different from inferred name.
*
* If your table doesn't follow our table name convention you can set this to the
* name of your table to explicitly tell ActiveRecord what your table is called.
*
* @var string
*/
static $table_name;
/**
* Set this to override the default primary key name if different from default name of "id".
*
* @var string
*/
static $primary_key;
/**
* Set this to explicitly specify the sequence name for the table.
*
* @var string
*/
static $sequence;
/**
* Set this to true in your subclass to use caching for this model.
* Note that you must also configure a cache object.
*/
static $cache = false;
/**
* Set this to specify an expiration period for this model.
* If not set, the expire value you set in your cache options will be used.
*
* @var integer
*/
static $cache_expire;
/**
* Allows you to create aliases for attributes.
*
* <code>
* class Person extends ActiveRecord\Model {
* static $alias_attribute = array(
* 'alias_first_name' => 'first_name',
* 'alias_last_name' => 'last_name');
* }
*
* $person = Person::first();
* $person->alias_first_name = 'Tito';
* echo $person->alias_first_name;
* </code>
*
* @var array
*/
static $alias_attribute = array();
/**
* Whitelist of attributes that are checked from mass-assignment calls such as constructing a model or using update_attributes.
*
* This is the opposite of {@link attr_protected $attr_protected}.
*
* <code>
* class Person extends ActiveRecord\Model {
* static $attr_accessible = array('first_name','last_name');
* }
*
* $person = new Person(array(
* 'first_name' => 'Tito',
* 'last_name' => 'the Grief',
* 'id' => 11111));
*
* echo $person->id; # => null
* </code>
*
* @var array
*/
static $attr_accessible = array();
/**
* Blacklist of attributes that cannot be mass-assigned.
*
* This is the opposite of {@link attr_accessible $attr_accessible} and the format
* for defining these are exactly the same.
*
* If the attribute is both accessible and protected, it is treated as protected.
*
* @var array
*/
static $attr_protected = array();
/**
* Delegates calls to a relationship.
*
* <code>
* class Person extends ActiveRecord\Model {
* static $belongs_to = array(array('venue'),array('host'));
* static $delegate = array(
* array('name', 'state', 'to' => 'venue'),
* array('name', 'to' => 'host', 'prefix' => 'woot'));
* }
* </code>
*
* Can then do:
*
* <code>
* $person->state # same as calling $person->venue->state
* $person->name # same as calling $person->venue->name
* $person->woot_name # same as calling $person->host->name
* </code>
*
* @var array
*/
static $delegate = array();
/**
* Constructs a model.
*
* When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)
* then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given
* $attributes will be mapped via set_attributes_via_mass_assignment.
*
* <code>
* new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
* </code>
*
* @param array $attributes Hash containing names and values to mass assign to the model
* @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes
* @param boolean $instantiating_via_find Set to true if this model is being created from a find call
* @param boolean $new_record Set to true if this should be considered a new record
* @return Model
*/
public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
{
$this->__new_record = $new_record;
// initialize attributes applying defaults
if (!$instantiating_via_find)
{
foreach (static::table()->columns as $name => $meta)
$this->attributes[$meta->inflected_name] = $meta->default;
}
$this->set_attributes_via_mass_assignment($attributes, $guard_attributes);
// since all attribute assignment now goes thru assign_attributes() we want to reset
// dirty if instantiating via find since nothing is really dirty when doing that
if ($instantiating_via_find)
$this->__dirty = array();
$this->invoke_callback('after_construct',false);
}
/**
* Magic method which delegates to read_attribute(). This handles firing off getter methods,
* as they are not checked/invoked inside of read_attribute(). This circumvents the problem with
* a getter being accessed with the same name as an actual attribute.
*
* You can also define customer getter methods for the model.
*
* EXAMPLE:
* <code>
* class User extends ActiveRecord\Model {
*
* # define custom getter methods. Note you must
* # prepend get_ to your method name:
* function get_middle_initial() {
* return $this->middle_name{0};
* }
* }
*
* $user = new User();
* echo $user->middle_name; # will call $user->get_middle_name()
* </code>
*
* If you define a custom getter with the same name as an attribute then you
* will need to use read_attribute() to get the attribute's value.
* This is necessary due to the way __get() works.
*
* For example, assume 'name' is a field on the table and we're defining a
* custom getter for 'name':
*
* <code>
* class User extends ActiveRecord\Model {
*
* # INCORRECT way to do it
* # function get_name() {
* # return strtoupper($this->name);
* # }
*
* function get_name() {
* return strtoupper($this->read_attribute('name'));
* }
* }
*
* $user = new User();
* $user->name = 'bob';
* echo $user->name; # => BOB
* </code>
*
*
* @see read_attribute()
* @param string $name Name of an attribute
* @return mixed The value of the attribute
*/
public function &__get($name)
{
// check for getter
if (method_exists($this, "get_$name"))
{
$name = "get_$name";
$value = $this->$name();
return $value;
}
return $this->read_attribute($name);
}
/**
* Determines if an attribute exists for this {@link Model}.
*
* @param string $attribute_name
* @return boolean
*/
public function __isset($attribute_name)
{
return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);
}
/**
* Magic allows un-defined attributes to set via $attributes.
*
* You can also define customer setter methods for the model.
*
* EXAMPLE:
* <code>
* class User extends ActiveRecord\Model {
*
* # define custom setter methods. Note you must
* # prepend set_ to your method name:
* function set_password($plaintext) {
* $this->encrypted_password = md5($plaintext);
* }
* }
*
* $user = new User();
* $user->password = 'plaintext'; # will call $user->set_password('plaintext')
* </code>
*
* If you define a custom setter with the same name as an attribute then you
* will need to use assign_attribute() to assign the value to the attribute.
* This is necessary due to the way __set() works.
*
* For example, assume 'name' is a field on the table and we're defining a
* custom setter for 'name':
*
* <code>
* class User extends ActiveRecord\Model {
*
* # INCORRECT way to do it
* # function set_name($name) {
* # $this->name = strtoupper($name);
* # }
*
* function set_name($name) {
* $this->assign_attribute('name',strtoupper($name));
* }
* }
*
* $user = new User();
* $user->name = 'bob';
* echo $user->name; # => BOB
* </code>
*
* @throws {@link UndefinedPropertyException} if $name does not exist
* @param string $name Name of attribute, relationship or other to set
* @param mixed $value The value
* @return mixed The value
*/
public function __set($name, $value)
{
if (array_key_exists($name, static::$alias_attribute))
$name = static::$alias_attribute[$name];
elseif (method_exists($this,"set_$name"))
{
$name = "set_$name";
return $this->$name($value);
}
if (array_key_exists($name,$this->attributes))
return $this->assign_attribute($name,$value);
if ($name == 'id')
return $this->assign_attribute($this->get_primary_key(true),$value);
foreach (static::$delegate as &$item)
{
if (($delegated_name = $this->is_delegated($name,$item)))
return $this->{$item['to']}->{$delegated_name} = $value;
}
throw new UndefinedPropertyException(get_called_class(),$name);
}
public function __wakeup()
{
// make sure the models Table instance gets initialized when waking up
static::table();
}
/**
* Assign a value to an attribute.
*
* @param string $name Name of the attribute
* @param mixed &$value Value of the attribute
* @return mixed the attribute value
*/
public function assign_attribute($name, $value)
{
$table = static::table();
if (!is_object($value)) {
if (array_key_exists($name, $table->columns)) {
$value = $table->columns[$name]->cast($value, static::connection());
} else {
$col = $table->get_column_by_inflected_name($name);
if (!is_null($col)){
$value = $col->cast($value, static::connection());
}
}
}
// convert php's \DateTime to ours
if ($value instanceof \DateTime) {
$date_class = Config::instance()->get_date_class();
if (!($value instanceof $date_class))
$value = $date_class::createFromFormat(
Connection::DATETIME_TRANSLATE_FORMAT,
$value->format(Connection::DATETIME_TRANSLATE_FORMAT),
$value->getTimezone()
);
}
if ($value instanceof DateTimeInterface)
// Tell the Date object that it's associated with this model and attribute. This is so it
// has the ability to flag this model as dirty if a field in the Date object changes.
$value->attribute_of($this,$name);
$this->attributes[$name] = $value;
$this->flag_dirty($name);
return $value;
}
/**
* Retrieves an attribute's value or a relationship object based on the name passed. If the attribute
* accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is
* for the primary key.
*
* @param string $name Name of an attribute
* @return mixed The value of the attribute
* @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...
*/
public function &read_attribute($name)
{
// check for aliased attribute
if (array_key_exists($name, static::$alias_attribute))
$name = static::$alias_attribute[$name];
// check for attribute
if (array_key_exists($name,$this->attributes))
return $this->attributes[$name];
// check relationships if no attribute
if (array_key_exists($name,$this->__relationships))
return $this->__relationships[$name];
$table = static::table();
// this may be first access to the relationship so check Table
if (($relationship = $table->get_relationship($name)))
{
$this->__relationships[$name] = $relationship->load($this);
return $this->__relationships[$name];
}
if ($name == 'id')
{
$pk = $this->get_primary_key(true);
if (isset($this->attributes[$pk]))
return $this->attributes[$pk];
}
//do not remove - have to return null by reference in strict mode
$null = null;
foreach (static::$delegate as &$item)
{
if (($delegated_name = $this->is_delegated($name,$item)))
{
$to = $item['to'];
if ($this->$to)
{
$val =& $this->$to->__get($delegated_name);
return $val;
}
else
return $null;
}
}
throw new UndefinedPropertyException(get_called_class(),$name);
}
/**
* Flags an attribute as dirty.
*
* @param string $name Attribute name
*/
public function flag_dirty($name)
{
if (!$this->__dirty)
$this->__dirty = array();
$this->__dirty[$name] = true;
}
/**
* Returns hash of attributes that have been modified since loading the model.
*
* @return mixed null if no dirty attributes otherwise returns array of dirty attributes.
*/
public function dirty_attributes()
{
if (!$this->__dirty)
return null;
$dirty = array_intersect_key($this->attributes,$this->__dirty);
return !empty($dirty) ? $dirty : null;
}
/**
* Check if a particular attribute has been modified since loading the model.
* @param string $attribute Name of the attribute
* @return boolean TRUE if it has been modified.
*/
public function attribute_is_dirty($attribute)
{
return $this->__dirty && isset($this->__dirty[$attribute]) && array_key_exists($attribute, $this->attributes);
}
/**
* Returns a copy of the model's attributes hash.
*
* @return array A copy of the model's attribute data
*/
public function attributes()
{
return $this->attributes;
}
/**
* Retrieve the primary key name.
*
* @param boolean Set to true to return the first value in the pk array only
* @return string The primary key for the model
*/
public function get_primary_key($first=false)
{
$pk = static::table()->pk;
return $first ? $pk[0] : $pk;
}
/**
* Returns the actual attribute name if $name is aliased.
*
* @param string $name An attribute name
* @return string
*/
public function get_real_attribute_name($name)
{
if (array_key_exists($name,$this->attributes))
return $name;
if (array_key_exists($name,static::$alias_attribute))
return static::$alias_attribute[$name];
return null;
}
/**
* Returns array of validator data for this Model.
*
* Will return an array looking like:
*
* <code>
* array(
* 'name' => array(
* array('validator' => 'validates_presence_of'),
* array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),
* 'password' => array(
* array('validator' => 'validates_length_of', 'minimum' => 6))
* )
* );
* </code>
*
* @return array An array containing validator data for this model.
*/
public function get_validation_rules()
{
require_once 'Validations.php';
$validator = new Validations($this);
return $validator->rules();
}
/**
* Returns an associative array containing values for all the attributes in $attributes
*
* @param array $attributes Array containing attribute names
* @return array A hash containing $name => $value
*/
public function get_values_for($attributes)
{
$ret = array();
foreach ($attributes as $name)
{
if (array_key_exists($name,$this->attributes))
$ret[$name] = $this->attributes[$name];
}
return $ret;
}
/**
* Retrieves the name of the table for this Model.
*
* @return string
*/
public static function table_name()
{
return static::table()->table;
}
/**
* Returns the attribute name on the delegated relationship if $name is
* delegated or null if not delegated.
*
* @param string $name Name of an attribute
* @param array $delegate An array containing delegate data
* @return delegated attribute name or null
*/
private function is_delegated($name, &$delegate)
{
if ($delegate['prefix'] != '')
$name = substr($name,strlen($delegate['prefix'])+1);
if (is_array($delegate) && in_array($name,$delegate['delegate']))
return $name;
return null;
}
/**
* Determine if the model is in read-only mode.
*
* @return boolean
*/
public function is_readonly()
{
return $this->__readonly;
}
/**
* Determine if the model is a new record.
*
* @return boolean
*/
public function is_new_record()
{
return $this->__new_record;
}
/**
* Throws an exception if this model is set to readonly.
*
* @throws \ActiveRecord\ReadOnlyException
* @param string $method_name Name of method that was invoked on model for exception message
*/
private function verify_not_readonly($method_name)
{
if ($this->is_readonly())
throw new ReadOnlyException(get_class($this), $method_name);
}
/**
* Flag model as readonly.
*
* @param boolean $readonly Set to true to put the model into readonly mode
*/
public function readonly($readonly=true)
{
$this->__readonly = $readonly;
}
/**
* Retrieve the connection for this model.
*
* @return Connection
*/
public static function connection()
{
return static::table()->conn;
}
/**
* Re-establishes the database connection with a new connection.
*
* @return Connection
*/
public static function reestablish_connection()
{
return static::table()->reestablish_connection();
}
/**
* Returns the {@link Table} object for this model.
*
* Be sure to call in static scoping: static::table()
*
* @return Table
*/
public static function table()
{
return Table::load(get_called_class());
}
/**
* Creates a model and saves it to the database.
*
* @param array $attributes Array of the models attributes
* @param boolean $validate True if the validators should be run
* @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes
* @return Model
*/
public static function create($attributes, $validate=true, $guard_attributes=true)
{
$class_name = get_called_class();
$model = new $class_name($attributes, $guard_attributes);
$model->save($validate);
return $model;
}
/**
* Save the model to the database.
*
* This function will automatically determine if an INSERT or UPDATE needs to occur.
* If a validation or a callback for this model returns false, then the model will
* not be saved and this will return false.
*
* If saving an existing model only data that has changed will be saved.
*
* @param boolean $validate Set to true or false depending on if you want the validators to run or not
* @return boolean True if the model was saved to the database otherwise false
*/
public function save($validate=true)
{
$this->verify_not_readonly('save');
return $this->is_new_record() ? $this->insert($validate) : $this->update($validate);
}
/**
* Issue an INSERT sql statement for this model's attribute.
*
* @see save
* @param boolean $validate Set to true or false depending on if you want the validators to run or not
* @return boolean True if the model was saved to the database otherwise false
*/
private function insert($validate=true)
{
$this->verify_not_readonly('insert');
if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
return false;
$table = static::table();
if (!($attributes = $this->dirty_attributes()))
$attributes = $this->attributes;
$pk = $this->get_primary_key(true);
$use_sequence = false;
if ($table->sequence && !isset($attributes[$pk]))
{
if (($conn = static::connection()) instanceof OciAdapter)
{
// terrible oracle makes us select the nextval first
$attributes[$pk] = $conn->get_next_sequence_value($table->sequence);
$table->insert($attributes);
$this->attributes[$pk] = $attributes[$pk];
}
else
{
// unset pk that was set to null
if (array_key_exists($pk,$attributes))
unset($attributes[$pk]);
$table->insert($attributes,$pk,$table->sequence);
$use_sequence = true;
}
}
else
$table->insert($attributes);
// if we've got an autoincrementing/sequenced pk set it
// don't need this check until the day comes that we decide to support composite pks
// if (count($pk) == 1)
{
$column = $table->get_column_by_inflected_name($pk);
if ($column->auto_increment || $use_sequence)
$this->attributes[$pk] = static::connection()->insert_id($table->sequence);
}
$this->__new_record = false;
$this->invoke_callback('after_create',false);
$this->expire_cache();
return true;
}
/**
* Issue an UPDATE sql statement for this model's dirty attributes.
*
* @see save
* @param boolean $validate Set to true or false depending on if you want the validators to run or not
* @return boolean True if the model was saved to the database otherwise false
*/
private function update($validate=true)
{
$this->verify_not_readonly('update');
if ($validate && !$this->_validate())
return false;
if ($this->is_dirty())
{
$pk = $this->values_for_pk();
if (empty($pk))
throw new ActiveRecordException("Cannot update, no primary key defined for: " . get_called_class());
if (!$this->invoke_callback('before_update',false))
return false;
$dirty = $this->dirty_attributes();
static::table()->update($dirty,$pk);
$this->invoke_callback('after_update',false);
$this->expire_cache();
}
return true;
}
protected function expire_cache()
{
$table = static::table();
if($table->cache_individual_model)
{
Cache::delete($this->cache_key());
}
}
protected function cache_key()
{
$table = static::table();
return $table->cache_key_for_model($this->values_for_pk());
}
/**
* Deletes records matching conditions in $options
*
* Does not instantiate models and therefore does not invoke callbacks
*
* Delete all using a hash:
*
* <code>
* YourModel::delete_all(array('conditions' => array('name' => 'Tito')));
* </code>
*
* Delete all using an array:
*
* <code>
* YourModel::delete_all(array('conditions' => array('name = ?', 'Tito')));
* </code>
*
* Delete all using a string:
*
* <code>
* YourModel::delete_all(array('conditions' => 'name = "Tito"'));
* </code>
*
* An options array takes the following parameters:
*
* <ul>
* <li><b>conditions:</b> Conditions using a string/hash/array</li>
* <li><b>limit:</b> Limit number of records to delete (MySQL & Sqlite only)</li>
* <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
* </ul>
*
* @params array $options
* return integer Number of rows affected
*/
public static function delete_all($options=array())
{
$table = static::table();
$conn = static::connection();
$sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
$conditions = is_array($options) ? $options['conditions'] : $options;
if (is_array($conditions) && !is_hash($conditions))
call_user_func_array(array($sql, 'delete'), $conditions);
else
$sql->delete($conditions);
if (isset($options['limit']))
$sql->limit($options['limit']);
if (isset($options['order']))
$sql->order($options['order']);
$values = $sql->bind_values();
$ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
return $ret->rowCount();
}
/**
* Updates records using set in $options
*
* Does not instantiate models and therefore does not invoke callbacks
*
* Update all using a hash:
*
* <code>
* YourModel::update_all(array('set' => array('name' => "Bob")));
* </code>
*
* Update all using a string:
*
* <code>
* YourModel::update_all(array('set' => 'name = "Bob"'));
* </code>
*
* An options array takes the following parameters:
*
* <ul>
* <li><b>set:</b> String/hash of field names and their values to be updated with
* <li><b>conditions:</b> Conditions using a string/hash/array</li>
* <li><b>limit:</b> Limit number of records to update (MySQL & Sqlite only)</li>
* <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
* </ul>
*
* @params array $options
* return integer Number of rows affected
*/
public static function update_all($options=array())
{
$table = static::table();
$conn = static::connection();
$sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
$sql->update($options['set']);
if (isset($options['conditions']) && ($conditions = $options['conditions']))
{
if (is_array($conditions) && !is_hash($conditions))
call_user_func_array(array($sql, 'where'), $conditions);
else
$sql->where($conditions);
}
if (isset($options['limit']))
$sql->limit($options['limit']);
if (isset($options['order']))
$sql->order($options['order']);
$values = $sql->bind_values();
$ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
return $ret->rowCount();
}
/**
* Deletes this model from the database and returns true if successful.
*
* @return boolean
*/
public function delete()
{
$this->verify_not_readonly('delete');
$pk = $this->values_for_pk();
if (empty($pk))
throw new ActiveRecordException("Cannot delete, no primary key defined for: " . get_called_class());
if (!$this->invoke_callback('before_destroy',false))
return false;
static::table()->delete($pk);
$this->invoke_callback('after_destroy',false);
$this->expire_cache();
return true;
}
/**
* Helper that creates an array of values for the primary key(s).
*
* @return array An array in the form array(key_name => value, ...)
*/
public function values_for_pk()
{
return $this->values_for(static::table()->pk);
}
/**
* Helper to return a hash of values for the specified attributes.
*
* @param array $attribute_names Array of attribute names
* @return array An array in the form array(name => value, ...)
*/
public function values_for($attribute_names)
{
$filter = array();
foreach ($attribute_names as $name)
$filter[$name] = $this->$name;
return $filter;
}
/**
* Validates the model.
*
* @return boolean True if passed validators otherwise false
*/
private function _validate()
{
require_once 'Validations.php';
$validator = new Validations($this);
$validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
foreach (array('before_validation', "before_$validation_on") as $callback)
{
if (!$this->invoke_callback($callback,false))
return false;
}
// need to store reference b4 validating so that custom validators have access to add errors
$this->errors = $validator->get_record();
$validator->validate();
foreach (array('after_validation', "after_$validation_on") as $callback)
$this->invoke_callback($callback,false);
if (!$this->errors->is_empty())
return false;
return true;
}
/**
* Returns true if the model has been modified.
*
* @return boolean true if modified
*/
public function is_dirty()
{
return empty($this->__dirty) ? false : true;
}
/**
* Run validations on model and returns whether or not model passed validation.
*
* @see is_invalid
* @return boolean
*/
public function is_valid()
{
return $this->_validate();
}
/**
* Runs validations and returns true if invalid.
*
* @see is_valid
* @return boolean
*/
public function is_invalid()
{
return !$this->_validate();
}
/**
* Updates a model's timestamps.
*/
public function set_timestamps()
{
$now = date('Y-m-d H:i:s');
if (isset($this->updated_at))
$this->updated_at = $now;
if (isset($this->created_at) && $this->is_new_record())
$this->created_at = $now;
}
/**
* Mass update the model with an array of attribute data and saves to the database.
*
* @param array $attributes An attribute data array in the form array(name => value, ...)
* @return boolean True if successfully updated and saved otherwise false
*/
public function update_attributes($attributes)
{
$this->set_attributes($attributes);
return $this->save();
}
/**
* Updates a single attribute and saves the record without going through the normal validation procedure.
*
* @param string $name Name of attribute
* @param mixed $value Value of the attribute
* @return boolean True if successful otherwise false
*/
public function update_attribute($name, $value)
{
$this->__set($name, $value);
return $this->update(false);
}
/**
* Mass update the model with data from an attributes hash.
*
* Unlike update_attributes() this method only updates the model's data
* but DOES NOT save it to the database.
*
* @see update_attributes
* @param array $attributes An array containing data to update in the form array(name => value, ...)
*/
public function set_attributes(array $attributes)
{
$this->set_attributes_via_mass_assignment($attributes, true);
}
/**
* Passing $guard_attributes as true will throw an exception if an attribute does not exist.
*
* @throws \ActiveRecord\UndefinedPropertyException
* @param array $attributes An array in the form array(name => value, ...)
* @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded
*/
private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
{
//access uninflected columns since that is what we would have in result set
$table = static::table();
$exceptions = array();
$use_attr_accessible = !empty(static::$attr_accessible);
$use_attr_protected = !empty(static::$attr_protected);
$connection = static::connection();
foreach ($attributes as $name => $value)
{
// is a normal field on the table
if (array_key_exists($name,$table->columns))
{
$value = $table->columns[$name]->cast($value,$connection);
$name = $table->columns[$name]->inflected_name;
}
if ($guard_attributes)
{
if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
continue;
if ($use_attr_protected && in_array($name,static::$attr_protected))
continue;
// set valid table data
try {
$this->$name = $value;
} catch (UndefinedPropertyException $e) {
$exceptions[] = $e->getMessage();
}
}
else
{
// ignore OciAdapter's limit() stuff
if ($name == 'ar_rnum__')
continue;
// set arbitrary data
$this->assign_attribute($name,$value);
}
}
if (!empty($exceptions))
throw new UndefinedPropertyException(get_called_class(),$exceptions);
}
/**
* Add a model to the given named ($name) relationship.
*
* @internal This should <strong>only</strong> be used by eager load
* @param Model $model
* @param $name of relationship for this table
* @return void
*/
public function set_relationship_from_eager_load(Model $model=null, $name)
{
$table = static::table();
if (($rel = $table->get_relationship($name)))
{
if ($rel->is_poly())
{
// if the related model is null and it is a poly then we should have an empty array
if (is_null($model))
return $this->__relationships[$name] = array();
else
return $this->__relationships[$name][] = $model;
}
else
return $this->__relationships[$name] = $model;
}
throw new RelationshipException("Relationship named $name has not been declared for class: {$table->class->getName()}");
}
/**
* Reloads the attributes and relationships of this object from the database.
*
* @return Model
*/
public function reload()
{
$this->__relationships = array();
$pk = array_values($this->get_values_for($this->get_primary_key()));
$this->expire_cache();
$this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false);
$this->reset_dirty();
return $this;
}
public function __clone()
{
$this->__relationships = array();
$this->reset_dirty();
return $this;
}
/**
* Resets the dirty array.
*
* @see dirty_attributes
*/
public function reset_dirty()
{
$this->__dirty = null;
}
/**
* A list of valid finder options.
*
* @var array
*/
static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
/**
* Enables the use of dynamic finders.
*
* Dynamic finders are just an easy way to do queries quickly without having to
* specify an options array with conditions in it.
*
* <code>
* SomeModel::find_by_first_name('Tito');
* SomeModel::find_by_first_name_and_last_name('Tito','the Grief');
* SomeModel::find_by_first_name_or_last_name('Tito','the Grief');
* SomeModel::find_all_by_last_name('Smith');
* SomeModel::count_by_name('Bob')
* SomeModel::count_by_name_or_state('Bob','VA')
* SomeModel::count_by_name_and_state('Bob','VA')
* </code>
*
* You can also create the model if the find call returned no results:
*
* <code>
* Person::find_or_create_by_name('Tito');
*
* # would be the equivalent of
* if (!Person::find_by_name('Tito'))
* Person::create(array('Tito'));
* </code>
*
* Some other examples of find_or_create_by:
*
* <code>
* Person::find_or_create_by_name_and_id('Tito',1);
* Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));
* </code>
*
* @param string $method Name of method
* @param mixed $args Method args
* @return Model
* @throws {@link ActiveRecordException} if invalid query
* @see find
*/
public static function __callStatic($method, $args)
{
$options = static::extract_and_validate_options($args);
$create = false;
if (substr($method,0,17) == 'find_or_create_by')
{
$attributes = substr($method,17);
// can't take any finders with OR in it when doing a find_or_create_by
if (strpos($attributes,'_or_') !== false)
throw new ActiveRecordException("Cannot use OR'd attributes in find_or_create_by");
$create = true;
$method = 'find_by' . substr($method,17);
}
if (substr($method,0,7) === 'find_by')
{
$attributes = substr($method,8);
$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),$attributes,$args,static::$alias_attribute);
if (!($ret = static::find('first',$options)) && $create)
return static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));
return $ret;
}
elseif (substr($method,0,11) === 'find_all_by')
{
$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,12),$args,static::$alias_attribute);
return static::find('all',$options);
}
elseif (substr($method,0,8) === 'count_by')
{
$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,9),$args,static::$alias_attribute);
return static::count($options);
}
throw new ActiveRecordException("Call to undefined method: $method");
}
/**
* Enables the use of build|create for associations.
*
* @param string $method Name of method
* @param mixed $args Method args
* @return mixed An instance of a given {@link AbstractRelationship}
*/
public function __call($method, $args)
{
//check for build|create_association methods
if (preg_match('/(build|create)_/', $method))
{
if (!empty($args))
$args = $args[0];
$association_name = str_replace(array('build_', 'create_'), '', $method);
$method = str_replace($association_name, 'association', $method);
$table = static::table();
if (($association = $table->get_relationship($association_name)) ||
($association = $table->get_relationship(($association_name = Utils::pluralize($association_name)))))
{
// access association to ensure that the relationship has been loaded
// so that we do not double-up on records if we append a newly created
$this->$association_name;
return $association->$method($this, $args);
}
}
throw new ActiveRecordException("Call to undefined method: $method");
}
/**
* Alias for self::find('all').
*
* @see find
* @return array array of records found
*/
public static function all(/* ... */)
{
return call_user_func_array('static::find',array_merge(array('all'),func_get_args()));
}
/**
* Get a count of qualifying records.
*
* <code>
* YourModel::count(array('conditions' => 'amount > 3.14159265'));
* </code>
*
* @see find
* @return int Number of records that matched the query
*/
public static function count(/* ... */)
{
$args = func_get_args();
$options = static::extract_and_validate_options($args);
$options['select'] = 'COUNT(*)';
if (!empty($args) && !is_null($args[0]) && !empty($args[0]))
{
if (is_hash($args[0]))
$options['conditions'] = $args[0];
else
$options['conditions'] = call_user_func_array('static::pk_conditions',$args);
}
$table = static::table();
$sql = $table->options_to_sql($options);
$values = $sql->get_where_values();
return static::connection()->query_and_fetch_one($sql->to_s(),$values);
}
/**
* Determine if a record exists.
*
* <code>
* SomeModel::exists(123);
* SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));
* SomeModel::exists(array('id' => 123, 'name' => 'Tito'));
* </code>
*
* @see find
* @return boolean
*/
public static function exists(/* ... */)
{
return call_user_func_array('static::count',func_get_args()) > 0 ? true : false;
}
/**
* Alias for self::find('first').
*
* @see find
* @return Model The first matched record or null if not found
*/
public static function first(/* ... */)
{
return call_user_func_array('static::find',array_merge(array('first'),func_get_args()));
}
/**
* Alias for self::find('last')
*
* @see find
* @return Model The last matched record or null if not found
*/
public static function last(/* ... */)
{
return call_user_func_array('static::find',array_merge(array('last'),func_get_args()));
}
/**
* Find records in the database.
*
* Finding by the primary key:
*
* <code>
* # queries for the model with id=123
* YourModel::find(123);
*
* # queries for model with id in(1,2,3)
* YourModel::find(1,2,3);
*
* # finding by pk accepts an options array
* YourModel::find(123,array('order' => 'name desc'));
* </code>
*
* Finding by using a conditions array:
*
* <code>
* YourModel::find('first', array('conditions' => array('name=?','Tito'),
* 'order' => 'name asc'))
* YourModel::find('all', array('conditions' => 'amount > 3.14159265'));
* YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));
* </code>
*
* Finding by using a hash:
*
* <code>
* YourModel::find(array('name' => 'Tito', 'id' => 1));
* YourModel::find('first',array('name' => 'Tito', 'id' => 1));
* YourModel::find('all',array('name' => 'Tito', 'id' => 1));
* </code>
*
* An options array can take the following parameters:
*
* <ul>
* <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>
* <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>
* <li><b>include:</b> TODO not implemented yet</li>
* <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')),
* array('name' => 'Tito', 'id' => 1)</li>
* <li><b>limit:</b> Number of records to limit the query to</li>
* <li><b>offset:</b> The row offset to return results from for the query</li>
* <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>
* <li><b>readonly:</b> Return all the models in readonly mode</li>
* <li><b>group:</b> A SQL group by fragment</li>
* </ul>
*
* @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched
* @return mixed An array of records found if doing a find_all otherwise a
* single Model object or null if it wasn't found. NULL is only return when
* doing a first/last find. If doing an all find and no records matched this
* will return an empty array.
*/
public static function find(/* $type, $options */)
{
$class = get_called_class();
if (func_num_args() <= 0)
throw new RecordNotFound("Couldn't find $class without an ID");
$args = func_get_args();
$options = static::extract_and_validate_options($args);
$num_args = count($args);
$single = true;
if ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))
{
switch ($args[0])
{
case 'all':
$single = false;
break;
case 'last':
if (!array_key_exists('order',$options))
$options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';
else
$options['order'] = SQLBuilder::reverse_order($options['order']);
// fall thru
case 'first':
$options['limit'] = 1;
$options['offset'] = 0;
break;
}
$args = array_slice($args,1);
$num_args--;
}
//find by pk
elseif (1 === count($args) && 1 == $num_args)
$args = $args[0];
// anything left in $args is a find by pk
if ($num_args > 0 && !isset($options['conditions']))
return static::find_by_pk($args, $options);
$options['mapped_names'] = static::$alias_attribute;
$list = static::table()->find($options);
return $single ? (!empty($list) ? $list[0] : null) : $list;
}
/**
* Will look up a list of primary keys from cache
*
* @param mixed $pks primary keys
* @return array
*/
protected static function get_models_from_cache($pks, $options)
{
$models = array();
$table = static::table();
if(!is_array($pks))
{
$pks = array($pks);
}
foreach($pks as $pk)
{
$options['conditions'] = static::pk_conditions($pk);
$models[] = Cache::get($table->cache_key_for_model($pk), function() use ($table, $options)
{
$res = $table->find($options);
return $res ? $res[0] : null;
}, $table->cache_model_expire);
}
return array_filter($models);
}
/**
* Finder method which will find by a single or array of primary keys for this model.
*
* @see find
* @param array $values An array containing values for the pk
* @param array $options An options array
* @return Model
* @throws {@link RecordNotFound} if a record could not be found
*/
public static function find_by_pk($values, $options)
{
if($values===null)
{
throw new RecordNotFound("Couldn't find ".get_called_class()." without an ID");
}
$table = static::table();
if($table->cache_individual_model)
{
$list = static::get_models_from_cache($values, $options);
}
else
{
$options['conditions'] = static::pk_conditions($values);
$list = $table->find($options);
}
$results = count($list);
if ($results != ($expected = count($values)))
{
$class = get_called_class();
if (is_array($values))
$values = join(',',$values);
if ($expected == 1)
{
throw new RecordNotFound("Couldn't find $class with ID=$values");
}
throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
}
return $expected == 1 ? $list[0] : $list;
}
/**
* Find using a raw SELECT query.
*
* <code>
* YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito'));
* YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'");
* </code>
*
* @param string $sql The raw SELECT query
* @param array $values An array of values for any parameters that needs to be bound
* @return array An array of models
*/
public static function find_by_sql($sql, $values=null)
{
return static::table()->find_by_sql($sql, $values, true);
}
/**
* Helper method to run arbitrary queries against the model's database connection.
*
* @param string $sql SQL to execute
* @param array $values Bind values, if any, for the query
* @return object A PDOStatement object
*/
public static function query($sql, $values=null)
{
return static::connection()->query($sql, $values);
}
/**
* Determines if the specified array is a valid ActiveRecord options array.
*
* @param array $array An options array
* @param bool $throw True to throw an exception if not valid
* @return boolean True if valid otherwise valse
* @throws {@link ActiveRecordException} if the array contained any invalid options
*/
public static function is_options_hash($array, $throw=true)
{
if (is_hash($array))
{
$keys = array_keys($array);
$diff = array_diff($keys,self::$VALID_OPTIONS);
if (!empty($diff) && $throw)
throw new ActiveRecordException("Unknown key(s): " . join(', ',$diff));
$intersect = array_intersect($keys,self::$VALID_OPTIONS);
if (!empty($intersect))
return true;
}
return false;
}
/**
* Returns a hash containing the names => values of the primary key.
*
* @internal This needs to eventually support composite keys.
* @param mixed $args Primary key value(s)
* @return array An array in the form array(name => value, ...)
*/
public static function pk_conditions($args)
{
$table = static::table();
$ret = array($table->pk[0] => $args);
return $ret;
}
/**
* Pulls out the options hash from $array if any.
*
* @internal DO NOT remove the reference on $array.
* @param array &$array An array
* @return array A valid options array
*/
public static function extract_and_validate_options(array &$array)
{
$options = array();
if ($array)
{
$last = &$array[count($array)-1];
try
{
if (self::is_options_hash($last))
{
array_pop($array);
$options = $last;
}
}
catch (ActiveRecordException $e)
{
if (!is_hash($last))
throw $e;
$options = array('conditions' => $last);
}
}
return $options;
}
/**
* Returns a JSON representation of this model.
*
* @see Serialization
* @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
* @return string JSON representation of the model
*/
public function to_json(array $options=array())
{
return $this->serialize('Json', $options);
}
/**
* Returns an XML representation of this model.
*
* @see Serialization
* @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)
* @return string XML representation of the model
*/
public function to_xml(array $options=array())
{
return $this->serialize('Xml', $options);
}
/**
* Returns an CSV representation of this model.
* Can take optional delimiter and enclosure
* (defaults are , and double quotes)
*
* Ex:
* <code>
* ActiveRecord\CsvSerializer::$delimiter=';';
* ActiveRecord\CsvSerializer::$enclosure='';
* YourModel::find('first')->to_csv(array('only'=>array('name','level')));
* returns: Joe,2
*
* YourModel::find('first')->to_csv(array('only_header'=>true,'only'=>array('name','level')));
* returns: name,level
* </code>
*
* @see Serialization
* @param array $options An array containing options for csv serialization (see {@link Serialization} for valid options)
* @return string CSV representation of the model
*/
public function to_csv(array $options=array())
{
return $this->serialize('Csv', $options);
}
/**
* Returns an Array representation of this model.
*
* @see Serialization
* @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
* @return array Array representation of the model
*/
public function to_array(array $options=array())
{
return $this->serialize('Array', $options);
}
/**
* Creates a serializer based on pre-defined to_serializer()
*
* An options array can take the following parameters:
*
* <ul>
* <li><b>only:</b> a string or array of attributes to be included.</li>
* <li><b>excluded:</b> a string or array of attributes to be excluded.</li>
* <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
* along with the method's returned value</li>
* <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
* </ul>
*
* @param string $type Either Xml, Json, Csv or Array
* @param array $options Options array for the serializer
* @return string Serialized representation of the model
*/
private function serialize($type, $options)
{
require_once 'Serialization.php';
$class = "ActiveRecord\\{$type}Serializer";
$serializer = new $class($this, $options);
return $serializer->to_s();
}
/**
* Invokes the specified callback on this model.
*
* @param string $method_name Name of the call back to run.
* @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
* @return boolean True if invoked or null if not
*/
private function invoke_callback($method_name, $must_exist=true)
{
return static::table()->callback->invoke($this,$method_name,$must_exist);
}
/**
* Executes a block of code inside a database transaction.
*
* <code>
* YourModel::transaction(function()
* {
* YourModel::create(array("name" => "blah"));
* });
* </code>
*
* If an exception is thrown inside the closure the transaction will
* automatically be rolled back. You can also return false from your
* closure to cause a rollback:
*
* <code>
* YourModel::transaction(function()
* {
* YourModel::create(array("name" => "blah"));
* throw new Exception("rollback!");
* });
*
* YourModel::transaction(function()
* {
* YourModel::create(array("name" => "blah"));
* return false; # rollback!
* });
* </code>
*
* @param callable $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.
* @return boolean True if the transaction was committed, False if rolled back.
*/
public static function transaction($closure)
{
$connection = static::connection();
try
{
$connection->transaction();
if ($closure() === false)
{
$connection->rollback();
return false;
}
else
$connection->commit();
}
catch (\Exception $e)
{
$connection->rollback();
throw $e;
}
return true;
}
}
================================================
FILE: lib/Reflections.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
use ReflectionClass;
/**
* Simple class that caches reflections of classes.
*
* @package ActiveRecord
*/
class Reflections extends Singleton
{
/**
* Current reflections.
*
* @var array
*/
private $reflections = array();
/**
* Instantiates a new ReflectionClass for the given class.
*
* @param string $class Name of a class
* @return Reflections $this so you can chain calls like Reflections::instance()->add('class')->get()
*/
public function add($class=null)
{
$class = $this->get_class($class);
if (!isset($this->reflections[$class]))
$this->reflections[$class] = new ReflectionClass($class);
return $this;
}
/**
* Destroys the cached ReflectionClass.
*
* Put this here mainly for testing purposes.
*
* @param string $class Name of a class.
* @return void
*/
public function destroy($class)
{
if (isset($this->reflections[$class]))
$this->reflections[$class] = null;
}
/**
* Get a cached ReflectionClass.
*
* @param string $class Optional name of a class
* @return mixed null or a ReflectionClass instance
* @throws ActiveRecordException if class was not found
*/
public function get($class=null)
{
$class = $this->get_class($class);
if (isset($this->reflections[$class]))
return $this->reflections[$class];
throw new ActiveRecordException("Class not found: $class");
}
/**
* Retrieve a class name to be reflected.
*
* @param mixed $mixed An object or name of a class
* @return string
*/
private function get_class($mixed=null)
{
if (is_object($mixed))
return get_class($mixed);
if (!is_null($mixed))
return $mixed;
return $this->get_called_class();
}
}
================================================
FILE: lib/Relationship.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Interface for a table relationship.
*
* @package ActiveRecord
*/
interface InterfaceRelationship
{
public function __construct($options=array());
public function build_association(Model $model, $attributes=array(), $guard_attributes=true);
public function create_association(Model $model, $attributes=array(), $guard_attributes=true);
}
/**
* Abstract class that all relationships must extend from.
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
abstract class AbstractRelationship implements InterfaceRelationship
{
/**
* Name to be used that will trigger call to the relationship.
*
* @var string
*/
public $attribute_name;
/**
* Class name of the associated model.
*
* @var string
*/
public $class_name;
/**
* Name of the foreign key.
*
* @var string
*/
public $foreign_key = array();
/**
* Options of the relationship.
*
* @var array
*/
protected $options = array();
/**
* Is the relationship single or multi.
*
* @var boolean
*/
protected $poly_relationship = false;
/**
* List of valid options for relationships.
*
* @var array
*/
static protected $valid_association_options = array('class_name', 'class', 'foreign_key', 'conditions', 'select', 'readonly', 'namespace');
/**
* Constructs a relationship.
*
* @param array $options Options for the relationship (see {@link valid_association_options})
* @return mixed
*/
public function __construct($options=array())
{
$this->attribute_name = $options[0];
$this->options = $this->merge_association_options($options);
$relationship = strtolower(denamespace(get_called_class()));
if ($relationship === 'hasmany' || $relationship === 'hasandbelongstomany')
$this->poly_relationship = true;
if (isset($this->options['conditions']) && !is_array($this->options['conditions']))
$this->options['conditions'] = array($this->options['conditions']);
if (isset($this->options['class']))
$this->set_class_name($this->options['class']);
elseif (isset($this->options['class_name']))
$this->set_class_name($this->options['class_name']);
$this->attribute_name = strtolower(Inflector::instance()->variablize($this->attribute_name));
if (!$this->foreign_key && isset($this->options['foreign_key']))
$this->foreign_key = is_array($this->options['foreign_key']) ? $this->options['foreign_key'] : array($this->options['foreign_key']);
}
protected function get_table()
{
return Table::load($this->class_name);
}
/**
* What is this relationship's cardinality?
*
* @return bool
*/
public function is_poly()
{
return $this->poly_relationship;
}
/**
* Eagerly loads relationships for $models.
*
* This method takes an array of models, collects PK or FK (whichever is needed for relationship), then queries
* the related table by PK/FK and attaches the array of returned relationships to the appropriately named relationship on
* $models.
*
* @param Table $table
* @param $models array of model objects
* @param $attributes array of attributes from $models
* @param $includes array of eager load directives
* @param $query_keys -> key(s) to be queried for on included/related table
* @param $model_values_keys -> key(s)/value(s) to be used in query from model which is including
* @return void
*/
protected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes=array(), $query_keys=array(), $model_values_keys=array())
{
$values = array();
$options = $this->options;
$inflector = Inflector::instance();
$query_key = $query_keys[0];
$model_values_key = $model_values_keys[0];
foreach ($attributes as $column => $value)
$values[] = $value[$inflector->variablize($model_values_key)];
$values = array($values);
$conditions = SQLBuilder::create_conditions_from_underscored_string($table->conn,$query_key,$values);
if (isset($options['conditions']) && strlen($options['conditions'][0]) > 1)
Utils::add_condition($options['conditions'], $conditions);
else
$options['conditions'] = $conditions;
if (!empty($includes))
$options['include'] = $includes;
if (!empty($options['through'])) {
// save old keys as we will be reseting them below for inner join convenience
$pk = $this->primary_key;
$fk = $this->foreign_key;
$this->set_keys($this->get_table()->class->getName(), true);
if (!isset($options['class_name'])) {
$class = classify($options['through'], true);
if (isset($this->options['namespace']) && !class_exists($class))
$class = $this->options['namespace'].'\\'.$class;
$through_table = $class::table();
} else {
$class = $options['class_name'];
$relation = $class::table()->get_relationship($options['through']);
$through_table = $relation->get_table();
}
$options['joins'] = $this->construct_inner_join_sql($through_table, true);
$query_key = $this->primary_key[0];
// reset keys
$this->primary_key = $pk;
$this->foreign_key = $fk;
}
$options = $this->unset_non_finder_options($options);
$class = $this->class_name;
$related_models = $class::find('all', $options);
$used_models_map = array();
$related_models_map = array();
$model_values_key = $inflector->variablize($model_values_key);
$query_key = $inflector->variablize($query_key);
foreach ($related_models as $related)
{
$related_models_map[$related->$query_key][] = $related;
}
foreach ($models as $model)
{
$key_to_match = $model->$model_values_key;
if (isset($related_models_map[$key_to_match])) {
foreach ($related_models_map[$key_to_match] as $related)
{
$hash = spl_object_hash($related);
if (isset($used_models_map[$hash]))
$model->set_relationship_from_eager_load(clone($related), $this->attribute_name);
else
$model->set_relationship_from_eager_load($related, $this->attribute_name);
$used_models_map[$hash] = true;
}
} else {
$model->set_relationship_from_eager_load(null, $this->attribute_name);
}
}
}
/**
* Creates a new instance of specified {@link Model} with the attributes pre-loaded.
*
* @param Model $model The model which holds this association
* @param array $attributes Hash containing attributes to initialize the model with
* @return Model
*/
public function build_association(Model $model, $attributes=array(), $guard_attributes=true)
{
$class_name = $this->class_name;
return new $class_name($attributes, $guard_attributes);
}
/**
* Creates a new instance of {@link Model} and invokes save.
*
* @param Model $model The model which holds this association
* @param array $attributes Hash containing attributes to initialize the model with
* @return Model
*/
public function create_association(Model $model, $attributes=array(), $guard_attributes=true)
{
$class_name = $this->class_name;
$new_record = $class_name::create($attributes, true, $guard_attributes);
return $this->append_record_to_associate($model, $new_record);
}
protected function append_record_to_associate(Model $associate, Model $record)
{
$association =& $associate->{$this->attribute_name};
if ($this->poly_relationship)
$association[] = $record;
else
$association = $record;
return $record;
}
protected function merge_association_options($options)
{
$available_options = array_merge(self::$valid_association_options,static::$valid_association_options);
$valid_options = array_intersect_key(array_flip($available_options),$options);
foreach ($valid_options as $option => $v)
$valid_options[$option] = $options[$option];
return $valid_options;
}
protected function unset_non_finder_options($options)
{
foreach (array_keys($options) as $option)
{
if (!in_array($option, Model::$VALID_OPTIONS))
unset($options[$option]);
}
return $options;
}
/**
* Infers the $this->class_name based on $this->attribute_name.
*
* Will try to guess the appropriate class by singularizing and uppercasing $this->attribute_name.
*
* @return void
* @see attribute_name
*/
protected function set_inferred_class_name()
{
$singularize = ($this instanceOf HasMany ? true : false);
$this->set_class_name(classify($this->attribute_name, $singularize));
}
protected function set_class_name($class_name)
{
if (!has_absolute_namespace($class_name) && isset($this->options['namespace'])) {
$class_name = $this->options['namespace'].'\\'.$class_name;
}
$reflection = Reflections::instance()->add($class_name)->get($class_name);
if (!$reflection->isSubClassOf('ActiveRecord\\Model'))
throw new RelationshipException("'$class_name' must extend from ActiveRecord\\Model");
$this->class_name = $class_name;
}
protected function create_conditions_from_keys(Model $model, $condition_keys=array(), $value_keys=array())
{
$condition_string = implode('_and_', $condition_keys);
$condition_values = array_values($model->get_values_for($value_keys));
// return null if all the foreign key values are null so that we don't try to do a query like "id is null"
if (all(null,$condition_values))
return null;
$conditions = SQLBuilder::create_conditions_from_underscored_string(Table::load(get_class($model))->conn,$condition_string,$condition_values);
# DO NOT CHANGE THE NEXT TWO LINES. add_condition operates on a reference and will screw options array up
if (isset($this->options['conditions']))
$options_conditions = $this->options['conditions'];
else
$options_conditions = array();
return Utils::add_condition($options_conditions, $conditions);
}
/**
* Creates INNER JOIN SQL for associations.
*
* @param Table $from_table the table used for the FROM SQL statement
* @param bool $using_through is this a THROUGH relationship?
* @param string $alias a table alias for when a table is being joined twice
* @return string SQL INNER JOIN fragment
*/
public function construct_inner_join_sql(Table $from_table, $using_through=false, $alias=null)
{
if ($using_through)
{
$join_table = $from_table;
$join_table_name = $from_table->get_fully_qualified_table_name();
$from_table_name = Table::load($this->class_name)->get_fully_qualified_table_name();
}
else
{
$join_table = Table::load($this->class_name);
$join_table_name = $join_table->get_fully_qualified_table_name();
$from_table_name = $from_table->get_fully_qualified_table_name();
}
// need to flip the logic when the key is on the other table
if ($this instanceof HasMany || $this instanceof HasOne)
{
$this->set_keys($from_table->class->getName());
if ($using_through)
{
$foreign_key = $this->primary_key[0];
$join_primary_key = $this->foreign_key[0];
}
else
{
$join_primary_key = $this->foreign_key[0];
$foreign_key = $this->primary_key[0];
}
}
else
{
$foreign_key = $this->foreign_key[0];
$join_primary_key = $this->primary_key[0];
}
if (!is_null($alias))
{
$aliased_join_table_name = $alias = $this->get_table()->conn->quote_name($alias);
$alias .= ' ';
}
else
$aliased_join_table_name = $join_table_name;
return "INNER JOIN $join_table_name {$alias}ON($from_table_name.$foreign_key = $aliased_join_table_name.$join_primary_key)";
}
/**
* This will load the related model data.
*
* @param Model $model The model this relationship belongs to
*/
abstract function load(Model $model);
}
/**
* One-to-many relationship.
*
* <code>
* # Table: people
* # Primary key: id
* # Foreign key: school_id
* class Person extends ActiveRecord\Model {}
*
* # Table: schools
* # Primary key: id
* class School extends ActiveRecord\Model {
* static $has_many = array(
* array('people')
* );
* });
* </code>
*
* Example using options:
*
* <code>
* class Payment extends ActiveRecord\Model {
* static $belongs_to = array(
* array('person'),
* array('order')
* );
* }
*
* class Order extends ActiveRecord\Model {
* static $has_many = array(
* array('people',
* 'through' => 'payments',
* 'select' => 'people.*, payments.amount',
* 'conditions' => 'payments.amount < 200')
* );
* }
* </code>
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
* @see valid_association_options
*/
class HasMany extends AbstractRelationship
{
/**
* Valid options to use for a {@link HasMany} relationship.
*
* <ul>
* <li><b>limit/offset:</b> limit the number of records</li>
* <li><b>primary_key:</b> name of the primary_key of the association (defaults to "id")</li>
* <li><b>group:</b> GROUP BY clause</li>
* <li><b>order:</b> ORDER BY clause</li>
* <li><b>through:</b> name of a model</li>
* </ul>
*
* @var array
*/
static protected $valid_association_options = array('primary_key', 'order', 'group', 'having', 'limit', 'offset', 'through', 'source');
protected $primary_key;
private $has_one = false;
private $through;
/**
* Constructs a {@link HasMany} relationship.
*
* @param array $options Options for the association
* @return HasMany
*/
public function __construct($options=array())
{
parent::__construct($options);
if (isset($this->options['through']))
{
$this->through = $this->options['through'];
if (isset($this->options['source']))
$this->set_class_name($this->options['source']);
}
if (!$this->primary_key && isset($this->options['primary_key']))
$this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : array($this->options['primary_key']);
if (!$this->class_name)
$this->set_inferred_class_name();
}
protected function set_keys($model_class_name, $override=false)
{
//infer from class_name
if (!$this->foreign_key || $override)
$this->foreign_key = array(Inflector::instance()->keyify($model_class_name));
if (!$this->primary_key || $override)
$this->primary_key = Table::load($model_class_name)->pk;
}
public function load(Model $model)
{
$class_name = $this->class_name;
$this->set_keys(get_class($model));
// since through relationships depend on other relationships we can't do
// this initiailization in the constructor since the other relationship
// may not have been created yet and we only want this to run once
if (!isset($this->initialized))
{
if ($this->through)
{
// verify through is a belongs_to or has_many for access of keys
if (!($through_relationship = $this->get_table()->get_relationship($this->through)))
throw new HasManyThroughAssociationException("Could not find the association $this->through in model " . get_class($model));
if (!($through_relationship instanceof HasMany) && !($through_relationship instanceof BelongsTo))
throw new HasManyThroughAssociationException('has_many through can only use a belongs_to or has_many association');
// save old keys as we will be reseting them below for inner join convenience
$pk = $this->primary_key;
$fk = $this->foreign_key;
$this->set_keys($this->get_table()->class->getName(), true);
$class = $this->class_name;
$relation = $class::table()->get_relationship($this->through);
$through_table = $relation->get_table();
$this->options['joins'] = $this->construct_inner_join_sql($through_table, true);
// reset keys
$this->primary_key = $pk;
$this->foreign_key = $fk;
}
$this->initialized = true;
}
if (!($conditions = $this->create_conditions_from_keys($model, $this->foreign_key, $this->primary_key)))
return null;
$options = $this->unset_non_finder_options($this->options);
$options['conditions'] = $conditions;
return $class_name::find($this->poly_relationship ? 'all' : 'first',$options);
}
/**
* Get an array containing the key and value of the foreign key for the association
*
* @param Model $model
* @access private
* @return array
*/
private function get_foreign_key_for_new_association(Model $model)
{
$this->set_keys($model);
$primary_key = Inflector::instance()->variablize($this->foreign_key[0]);
return array(
$primary_key => $model->id,
);
}
private function inject_foreign_key_for_new_association(Model $model, &$attributes)
{
$primary_key = $this->get_foreign_key_for_new_association($model);
if (!isset($attributes[key($primary_key)]))
$attributes[key($primary_key)] = current($primary_key);
return $attributes;
}
public function build_association(Model $model, $attributes=array(), $guard_attributes=true)
{
$relationship_attributes = $this->get_foreign_key_for_new_association($model);
if ($guard_attributes) {
// First build the record with just our relationship attributes (unguarded)
$record = parent::build_association($model, $relationship_attributes, false);
// Then, set our normal attributes (using guarding)
$record->set_attributes($attributes);
} else {
// Merge our attributes
$attributes = array_merge($relationship_attributes, $attributes);
// First build the record with just our relationship attributes (unguarded)
$record = parent::build_association($model, $attributes, $guard_attributes);
}
return $record;
}
public function create_association(Model $model, $attributes=array(), $guard_attributes=true)
{
$relationship_attributes = $this->get_foreign_key_for_new_association($model);
if ($guard_attributes) {
// First build the record with just our relationship attributes (unguarded)
$record = parent::build_association($model, $relationship_attributes, false);
// Then, set our normal attributes (using guarding)
$record->set_attributes($attributes);
// Save our model, as a "create" instantly saves after building
$record->save();
} else {
// Merge our attributes
$attributes = array_merge($relationship_attributes, $attributes);
// First build the record with just our relationship attributes (unguarded)
$record = parent::create_association($model, $attributes, $guard_attributes);
}
return $record;
}
public function load_eagerly($models=array(), $attributes=array(), $includes, Table $table)
{
$this->set_keys($table->class->name);
$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes,$this->foreign_key, $table->pk);
}
}
/**
* One-to-one relationship.
*
* <code>
* # Table name: states
* # Primary key: id
* class State extends ActiveRecord\Model {}
*
* # Table name: people
* # Foreign key: state_id
* class Person extends ActiveRecord\Model {
* static $has_one = array(array('state'));
* }
* </code>
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
class HasOne extends HasMany
{
}
/**
* @todo implement me
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
class HasAndBelongsToMany extends AbstractRelationship
{
public function __construct($options=array())
{
/* options =>
* join_table - name of the join table if not in lexical order
* foreign_key -
* association_foreign_key - default is {assoc_class}_id
* uniq - if true duplicate assoc objects will be ignored
* validate
*/
}
public function load(Model $model)
{
}
}
/**
* Belongs to relationship.
*
* <code>
* class School extends ActiveRecord\Model {}
*
* class Person extends ActiveRecord\Model {
* static $belongs_to = array(
* array('school')
* );
* }
* </code>
*
* Example using options:
*
* <code>
* class School extends ActiveRecord\Model {}
*
* class Person extends ActiveRecord\Model {
* static $belongs_to = array(
* array('school', 'primary_key' => 'school_id')
* );
* }
* </code>
*
* @package ActiveRecord
* @see valid_association_options
* @see http://www.phpactiverecord.org/guides/associations
*/
class BelongsTo extends AbstractRelationship
{
public function __construct($options=array())
{
parent::__construct($options);
if (!$this->class_name)
$this->set_inferred_class_name();
//infer from class_name
if (!$this->foreign_key)
$this->foreign_key = array(Inflector::instance()->keyify($this->class_name));
}
public function __get($name)
{
if($name === 'primary_key' && !isset($this->primary_key)) {
$this->primary_key = array(Table::load($this->class_name)->pk[0]);
}
return $this->$name;
}
public function load(Model $model)
{
$keys = array();
$inflector = Inflector::instance();
foreach ($this->foreign_key as $key)
$keys[] = $inflector->variablize($key);
if (!($conditions = $this->create_conditions_from_keys($model, $this->primary_key, $keys)))
return null;
$options = $this->unset_non_finder_options($this->options);
$options['conditions'] = $conditions;
$class = $this->class_name;
return $class::first($options);
}
public function load_eagerly($models=array(), $attributes, $includes, Table $table)
{
$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes, $this->primary_key,$this->foreign_key);
}
}
================================================
FILE: lib/SQLBuilder.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Helper class for building sql statements progmatically.
*
* @package ActiveRecord
*/
class SQLBuilder
{
private $connection;
private $operation = 'SELECT';
private $table;
private $select = '*';
private $joins;
private $order;
private $limit;
private $offset;
private $group;
private $having;
private $update;
// for where
private $where;
private $where_values = array();
// for insert/update
private $data;
private $sequence;
/**
* Constructor.
*
* @param Connection $connection A database connection object
* @param string $table Name of a table
* @return SQLBuilder
* @throws ActiveRecordException if connection was invalid
*/
public function __construct($connection, $table)
{
if (!$connection)
throw new ActiveRecordException('A valid database connection is required.');
$this->connection = $connection;
$this->table = $table;
}
/**
* Returns the SQL string.
*
* @return string
*/
public function __toString()
{
return $this->to_s();
}
/**
* Returns the SQL string.
*
* @see __toString
* @return string
*/
public function to_s()
{
$func = 'build_' . strtolower($this->operation);
return $this->$func();
}
/**
* Returns the bind values.
*
* @return array
*/
public function bind_values()
{
$ret = array();
if ($this->data)
$ret = array_values($this->data);
if ($this->get_where_values())
$ret = array_merge($ret,$this->get_where_values());
return array_flatten($ret);
}
public function get_where_values()
{
return $this->where_values;
}
public function where(/* (conditions, values) || (hash) */)
{
$this->apply_where_conditions(func_get_args());
return $this;
}
public function order($order)
{
$this->order = $order;
return $this;
}
public function group($group)
{
$this->group = $group;
return $this;
}
public function having($having)
{
$this->having = $having;
return $this;
}
public function limit($limit)
{
$this->limit = intval($limit);
return $this;
}
public function offset($offset)
{
$this->offset = intval($offset);
return $this;
}
public function select($select)
{
$this->operation = 'SELECT';
$this->select = $select;
return $this;
}
public function joins($joins)
{
$this->joins = $joins;
return $this;
}
public function insert($hash, $pk=null, $sequence_name=null)
{
if (!is_hash($hash))
throw new ActiveRecordException('Inserting requires a hash.');
$this->operation = 'INSERT';
$this->data = $hash;
if ($pk && $sequence_name)
$this->sequence = array($pk,$sequence_name);
return $this;
}
public function update($mixed)
{
$this->operation = 'UPDATE';
if (is_hash($mixed))
$this->data = $mixed;
elseif (is_string($mixed))
$this->update = $mixed;
else
throw new ActiveRecordException('Updating requires a hash or string.');
return $this;
}
public function delete()
{
$this->operation = 'DELETE';
$this->apply_where_conditions(func_get_args());
return $this;
}
/**
* Reverses an order clause.
*/
public static function reverse_order($order)
{
if (!trim($order))
return $order;
$parts = explode(',',$order);
for ($i=0,$n=count($parts); $i<$n; ++$i)
{
$v = strtolower($parts[$i]);
if (strpos($v,' asc') !== false)
$parts[$i] = preg_replace('/asc/i','DESC',$parts[$i]);
elseif (strpos($v,' desc') !== false)
$parts[$i] = preg_replace('/desc/i','ASC',$parts[$i]);
else
$parts[$i] .= ' DESC';
}
return join(',',$parts);
}
/**
* Converts a string like "id_and_name_or_z" into a conditions value like array("id=? AND name=? OR z=?", values, ...).
*
* @param Connection $connection
* @param $name Underscored string
* @param $values Array of values for the field names. This is used
* to determine what kind of bind marker to use: =?, IN(?), IS NULL
* @param $map A hash of "mapped_column_name" => "real_column_name"
* @return A conditions array in the form array(sql_string, value1, value2,...)
*/
public static function create_conditions_from_underscored_string(Connection $connection, $name, &$values=array(), &$map=null)
{
if (!$name)
return null;
$parts = preg_split('/(_and_|_or_)/i',$name,-1,PREG_SPLIT_DELIM_CAPTURE);
$num_values = count($values);
$conditions = array('');
for ($i=0,$j=0,$n=count($parts); $i<$n; $i+=2,++$j)
{
if ($i >= 2)
$conditions[0] .= preg_replace(array('/_and_/i','/_or_/i'),array(' AND ',' OR '),$parts[$i-1]);
if ($j < $num_values)
{
if (!is_null($values[$j]))
{
$bind = is_array($values[$j]) ? ' IN(?)' : '=?';
$conditions[] = $values[$j];
}
else
$bind = ' IS NULL';
}
else
$bind = ' IS NULL';
// map to correct name if $map was supplied
$name = $map && isset($map[$parts[$i]]) ? $map[$parts[$i]] : $parts[$i];
$conditions[0] .= $connection->quote_name($name) . $bind;
}
return $conditions;
}
/**
* Like create_conditions_from_underscored_string but returns a hash of name => value array instead.
*
* @param string $name A string containing attribute names connected with _and_ or _or_
* @param $args Array of values for each attribute in $name
* @param $map A hash of "mapped_column_name" => "real_column_name"
* @return array A hash of array(name => value, ...)
*/
public static function create_hash_from_underscored_string($name, &$values=array(), &$map=null)
{
$parts = preg_split('/(_and_|_or_)/i',$name);
$hash = array();
for ($i=0,$n=count($parts); $i<$n; ++$i)
{
// map to correct name if $map was supplied
$name = $map && isset($map[$parts[$i]]) ? $map[$parts[$i]] : $parts[$i];
$hash[$name] = $values[$i];
}
return $hash;
}
/**
* prepends table name to hash of field names to get around ambiguous fields when SQL builder
* has joins
*
* @param array $hash
* @return array $new
*/
private function prepend_table_name_to_fields($hash=array())
{
$new = array();
$table = $this->connection->quote_name($this->table);
foreach ($hash as $key => $value)
{
$k = $this->connection->quote_name($key);
$new[$table.'.'.$k] = $value;
}
return $new;
}
private function apply_where_conditions($args)
{
require_once 'Expressions.php';
$num_args = count($args);
if ($num_args == 1 && is_hash($args[0]))
{
$hash = is_null($this->joins) ? $args[0] : $this->prepend_table_name_to_fields($args[0]);
$e = new Expressions($this->connection,$hash);
$this->where = $e->to_s();
$this->where_values = array_flatten($e->values());
}
elseif ($num_args > 0)
{
// if the values has a nested array then we'll need to use Expressions to expand the bind marker for us
$values = array_slice($args,1);
foreach ($values as $name => &$value)
{
if (is_array($value))
{
$e = new Expressions($this->connection,$args[0]);
$e->bind_values($values);
$this->where = $e->to_s();
$this->where_values = array_flatten($e->values());
return;
}
}
// no nested array so nothing special to do
$this->where = $args[0];
$this->where_values = &$values;
}
}
private function build_delete()
{
$sql = "DELETE FROM $this->table";
if ($this->where)
$sql .= " WHERE $this->where";
if ($this->connection->accepts_limit_and_order_for_update_and_delete())
{
if ($this->order)
$sql .= " ORDER BY $this->order";
if ($this->limit)
$sql = $this->connection->limit($sql,null,$this->limit);
}
return $sql;
}
private function build_insert()
{
require_once 'Expressions.php';
$keys = join(',',$this->quoted_key_names());
if ($this->sequence)
{
$sql =
"INSERT INTO $this->table($keys," . $this->connection->quote_name($this->sequence[0]) .
") VALUES(?," . $this->connection->next_sequence_value($this->sequence[1]) . ")";
}
else
$sql = "INSERT INTO $this->table($keys) VALUES(?)";
$e = new Expressions($this->connection,$sql,array_values($this->data));
return $e->to_s();
}
private function build_select()
{
$sql = "SELECT $this->select FROM $this->table";
if ($this->joins)
$sql .= ' ' . $this->joins;
if ($this->where)
$sql .= " WHERE $this->where";
if ($this->group)
$sql .= " GROUP BY $this->group";
if ($this->having)
$sql .= " HAVING $this->having";
if ($this->order)
$sql .= " ORDER BY $this->order";
if ($this->limit || $this->offset)
$sql = $this->connection->limit($sql,$this->offset,$this->limit);
return $sql;
}
private function build_update()
{
if (strlen($this->update) > 0)
$set = $this->update;
else
$set = join('=?, ', $this->quoted_key_names()) . '=?';
$sql = "UPDATE $this->table SET $set";
if ($this->where)
$sql .= " WHERE $this->where";
if ($this->connection->accepts_limit_and_order_for_update_and_delete())
{
if ($this->order)
$sql .= " ORDER BY $this->order";
if ($this->limit)
$sql = $this->connection->limit($sql,null,$this->limit);
}
return $sql;
}
private function quoted_key_names()
{
$keys = array();
foreach ($this->data as $key => $value)
$keys[] = $this->connection->quote_name($key);
return $keys;
}
}
================================================
FILE: lib/Serialization.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
use XmlWriter;
/**
* Base class for Model serializers.
*
* All serializers support the following options:
*
* <ul>
* <li><b>only:</b> a string or array of attributes to be included.</li>
* <li><b>except:</b> a string or array of attributes to be excluded.</li>
* <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
* along with the method's returned value</li>
* <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
* <li><b>only_method:</b> a method that's called and only the resulting array is serialized
* <li><b>skip_instruct:</b> set to true to skip the <?xml ...?> declaration.</li>
* </ul>
*
* Example usage:
*
* <code>
* # include the attributes id and name
* # run $model->encoded_description() and include its return value
* # include the comments association
* # include posts association with its own options (nested)
* $model->to_json(array(
* 'only' => array('id','name', 'encoded_description'),
* 'methods' => array('encoded_description'),
* 'include' => array('comments', 'posts' => array('only' => 'id'))
* ));
*
* # except the password field from being included
* $model->to_xml(array('except' => 'password')));
* </code>
*
* @package ActiveRecord
* @link http://www.phpactiverecord.org/guides/utilities#topic-serialization
*/
abstract class Serialization
{
protected $model;
protected $options;
protected $attributes;
/**
* The default format to serialize DateTime objects to.
*
* @see DateTime
*/
public static $DATETIME_FORMAT = 'iso8601';
/**
* Set this to true if the serializer needs to create a nested array keyed
* on the name of the included classes such as for xml serialization.
*
* Setting this to true will produce the following attributes array when
* the include option was used:
*
* <code>
* $user = array('id' => 1, 'name' => 'Tito',
* 'permissions' => array(
* 'permission' => array(
* array('id' => 100, 'name' => 'admin'),
* array('id' => 101, 'name' => 'normal')
* )
* )
* );
* </code>
*
* Setting to false will produce this:
*
* <code>
* $user = array('id' => 1, 'name' => 'Tito',
* 'permissions' => array(
* array('id' => 100, 'name' => 'admin'),
* array('id' => 101, 'name' => 'normal')
* )
* );
* </code>
*
* @var boolean
*/
protected $includes_with_class_name_element = false;
/**
* Constructs a {@link Serialization} object.
*
* @param Model $model The model to serialize
* @param array &$options Options for serialization
* @return Serialization
*/
public function __construct(Model $model, &$options)
{
$this->model = $model;
$this->options = $options;
$this->attributes = $model->attributes();
$this->parse_options();
}
private function parse_options()
{
$this->check_only();
$this->check_except();
$this->check_methods();
$this->check_include();
$this->check_only_method();
}
private function check_only()
{
if (isset($this->options['only']))
{
$this->options_to_a('only');
$exclude = array_diff(array_keys($this->attributes),$this->options['only']);
$this->attributes = array_diff_key($this->attributes,array_flip($exclude));
}
}
private function check_except()
{
if (isset($this->options['except']) && !isset($this->options['only']))
{
$this->options_to_a('except');
$this->attributes = array_diff_key($this->attributes,array_flip($this->options['except']));
}
}
private function check_methods()
{
if (isset($this->options['methods']))
{
$this->options_to_a('methods');
foreach ($this->options['methods'] as $method)
{
if (method_exists($this->model, $method))
$this->attributes[$method] = $this->model->$method();
}
}
}
private function check_only_method()
{
if (isset($this->options['only_method']))
{
$method = $this->options['only_method'];
if (method_exists($this->model, $method))
$this->attributes = $this->model->$method();
}
}
private function check_include()
{
if (isset($this->options['include']))
{
$this->options_to_a('include');
$serializer_class = get_class($this);
foreach ($this->options['include'] as $association => $options)
{
if (!is_array($options))
{
$association = $options;
$options = array();
}
try {
$assoc = $this->model->$association;
if ($assoc === null)
{
$this->attributes[$association] = null;
}
elseif (!is_array($assoc))
{
$serialized = new $serializer_class($assoc, $options);
$this->attributes[$association] = $serialized->to_a();;
}
else
{
$includes = array();
foreach ($assoc as $a)
{
$serialized = new $serializer_class($a, $options);
if ($this->includes_with_class_name_element)
$includes[strtolower(get_class($a))][] = $serialized->to_a();
else
$includes[] = $serialized->to_a();
}
$this->attributes[$association] = $includes;
}
} catch (UndefinedPropertyException $e) {
;//move along
}
}
}
}
final protected function options_to_a($key)
{
if (!is_array($this->options[$key]))
$this->options[$key] = array($this->options[$key]);
}
/**
* Returns the attributes array.
* @return array
*/
final public function to_a()
{
$date_class = Config::instance()->get_date_class();
foreach ($this->attributes as &$value)
{
if ($value instanceof $date_class)
$value = $value->format(self::$DATETIME_FORMAT);
}
return $this->attributes;
}
/**
* Returns the serialized object as a string.
* @see to_s
* @return string
*/
final public function __toString()
{
return $this->to_s();
}
/**
* Performs the serialization.
* @return string
*/
abstract public function to_s();
};
/**
* Array serializer.
*
* @package ActiveRecord
*/
class ArraySerializer extends Serialization
{
public static $include_root = false;
public function to_s()
{
return self::$include_root ? array(strtolower(get_class($this->model)) => $this->to_a()) : $this->to_a();
}
}
/**
* JSON serializer.
*
* @package ActiveRecord
*/
class JsonSerializer extends ArraySerializer
{
public static $include_root = false;
public function to_s()
{
parent::$include_root = self::$include_root;
return json_encode(parent::to_s());
}
}
/**
* XML serializer.
*
* @package ActiveRecord
*/
class XmlSerializer extends Serialization
{
private $writer;
public function __construct(Model $model, &$options)
{
$this->includes_with_class_name_element = true;
parent::__construct($model,$options);
}
public function to_s()
{
return $this->xml_encode();
}
private function xml_encode()
{
$this->writer = new XmlWriter();
$this->writer->openMemory();
$this->writer->startDocument('1.0', 'UTF-8');
$this->writer->startElement(strtolower(denamespace(($this->model))));
$this->write($this->to_a());
$this->writer->endElement();
$this->writer->endDocument();
$xml = $this->writer->outputMemory(true);
if (@$this->options['skip_instruct'] == true)
$xml = preg_replace('/<\?xml version.*?\?>/','',$xml);
return $xml;
}
private function write($data, $tag=null)
{
foreach ($data as $attr => $value)
{
if ($tag != null)
$attr = $tag;
if (is_array($value) || is_object($value))
{
if (!is_int(key($value)))
{
$this->writer->startElement($attr);
$this->write($value);
$this->writer->endElement();
}
else
$this->write($value, $attr);
continue;
}
$this->writer->writeElement($attr, $value);
}
}
}
/**
* CSV serializer.
*
* @package ActiveRecord
*/
class CsvSerializer extends Serialization
{
public static $delimiter = ',';
public static $enclosure = '"';
public function to_s()
{
if (@$this->options['only_header'] == true) return $this->header();
return $this->row();
}
private function header()
{
return $this->to_csv(array_keys($this->to_a()));
}
private function row()
{
return $this->to_csv($this->to_a());
}
private function to_csv($arr)
{
$outstream = fopen('php://temp', 'w');
fputcsv($outstream, $arr, self::$delimiter, self::$enclosure);
rewind($outstream);
$buffer = trim(stream_get_contents($outstream));
fclose($outstream);
return $buffer;
}
}
================================================
FILE: lib/Singleton.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* This implementation of the singleton pattern does not conform to the strong definition
* given by the "Gang of Four." The __construct() method has not be privatized so that
* a singleton pattern is capable of being achieved; however, multiple instantiations are also
* possible. This allows the user more freedom with this pattern.
*
* @package ActiveRecord
*/
abstract class Singleton
{
/**
* Array of cached singleton objects.
*
* @var array
*/
private static $instances = array();
/**
* Static method for instantiating a singleton object.
*
* @return object
*/
final public static function instance()
{
$class_name = get_called_class();
if (!isset(self::$instances[$class_name]))
self::$instances[$class_name] = new $class_name;
return self::$instances[$class_name];
}
/**
* Singleton objects should not be cloned.
*
* @return void
*/
final private function __clone() {}
/**
* Similar to a get_called_class() for a child class to invoke.
*
* @return string
*/
final protected function get_called_class()
{
$backtrace = debug_backtrace();
return get_class($backtrace[2]['object']);
}
}
================================================
FILE: lib/Table.php
================================================
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Manages reading and writing to a database table.
*
* This class manages a database table and is used by the Model class for
* reading and writing to its database table. There is one instance of Table
* for every table you have a model for.
*
* @package ActiveRecord
*/
class Table
{
private static $cache = array();
public $class;
public $conn;
public $pk;
public $last_sql;
// Name/value pairs of columns in this table
public $columns = array();
/**
* Name of the table.
*/
public $table;
/**
* Name of the database (optional)
*/
public $db_name;
/**
* Name of the sequence for this table (optional). Defaults to {$table}_seq
*/
public $sequence;
/**
* Whether to cache individual models or not (not to be confused with caching of table schemas).
*/
public $cache_individual_model;
/**
* Expiration period for model caching.
*/
public $cache_model_expire;
/**
* A instance of CallBack for this model/table
* @static
* @var object ActiveRecord\CallBack
*/
public $callback;
/**
* List of relationships for this table.
*/
private $relationships = array();
public static function load($model_class_name)
{
if (!isset(self::$cache[$model_class_name]))
{
/* do not place set_assoc in constructor..it will lead to infinite loop due to
relationships requesting the model's table, but the cache hasn't been set yet */
self::$cache[$model_class_name] = new Table($model_class_name);
self::$cache[$model_class_name]->set_associations();
}
return self::$cache[$model_class_name];
}
public static function clear_cache($model_class_name=null)
{
if ($model_class_name && array_key_exists($model_class_name,self::$cache))
unset(self::$cache[$model_class_name]);
else
self::$cache = array();
}
public function __construct($class_name)
{
$this->class = Reflections::instance()->add($class_name)->get($class_name);
$this->reestablish_connection(false);
$this->set_table_name();
$this->get_meta_data();
$this->set_primary_key();
$this->set_sequence_name();
$this->set_delegates();
$this->set_cache();
$this->set_setters_and_getters();
$this->callback = new CallBack($class_name);
$this->callback->register('before_save', function(Model $model) { $model->set_timestamps(); }, array('prepend' => true));
$this->callback->register('after_save', function(Model $model) { $model->reset_dirty(); }, array('prepend' => true));
}
public function reestablish_connection($close=true)
{
// if connection name property is null the connection manager will use the default connection
$connection = $this->class->getStaticPropertyValue('connection',null);
if ($close)
{
ConnectionManager::drop_connection($connection);
static::clear_cache();
}
return ($this->conn = ConnectionManager::get_connection($connection));
}
public function create_joins($joins)
{
if (!is_array($joins))
return $joins;
$ret = $space = '';
$existing_tables = array();
foreach ($joins as $value)
{
$ret .= $space;
if (stripos($value,'JOIN ') === false)
{
if (array_key_exists($value, $this->relationships))
{
$rel = $this->get_relationship($value);
// if there is more than 1 join for a given table we need to alias the table names
if (array_key_exists($rel->class_name, $existing_tables))
{
$alias = $value;
$existing_tables[$rel->class_name]++;
}
else
{
$existing_tables[$rel->class_name] = true;
$alias = null;
}
$ret .= $rel->construct_inner_join_sql($this, false, $alias);
}
else
throw new RelationshipException("Relationship named $value has not been declared for class: {$this->class->getName()}");
}
else
$ret .= $value;
$space = ' ';
}
return $ret;
}
public function options_to_sql($options)
{
$table = array_key_exists('from', $options) ? $options['from'] : $this->get_fully_qualified_table_name();
$sql = new SQLBuilder($this->conn, $table);
if (array_key_exists('joins',$options))
{
$sql->joins($this->create_joins($options['joins']));
// by default, an inner join will not fetch the fields from the joined table
if (!array_key_exists('select', $options))
$options['select'] = $this->get_fully_qualified_table_name() . '.*';
}
if (array_key_exists('select',$options))
$sql->select($options['select']);
if (array_key_exists('conditions',$options))
{
if (!is_hash($options['conditions']))
{
if (is_string($options['conditions']))
$options['conditions'] = array($options['conditions']);
call_user_func_array(array($sql,'where'),$options['conditions']);
}
else
{
if (!empty($options['mapped_names']))
$options['conditions'] = $this->map_names($options['conditions'],$options['mapped_names']);
$sql->where($options['conditions']);
}
}
if (array_key_exists('order',$options))
$sql->order($options['order']);
if (array_key_exists('limit',$options))
$sql->limit($options['limit']);
if (array_key_exists('offset',$options))
$sql->offset($options['offset']);
if (array_key_exists('group',$options))
$sql->group($options['group']);
if (array_key_exists('having',$options))
$sql->having($options['having']);
return $sql;
}
public function find($options)
{
$sql = $this->options_to_sql($options);
$readonly = (array_key_exists('readonly',$options) && $options['readonly']) ? true : false;
$eager_load = array_key_exists('include',$options) ? $options['include'] : null;
return $this->find_by_sql($sql->to_s(),$sql->get_where_values(), $readonly, $eager_load);
}
public function cache_key_for_model($pk)
{
if (is_array($pk))
{
$pk = implode('-', $pk);
}
return $this->class->name . '-' . $pk;
}
public function find_by_sql($sql, $values=null, $readonly=false, $includes=null)
{
$this->last_sql = $sql;
$collect_attrs_for_includes = is_null($includes) ? false : true;
$list = $attrs = array();
$sth = $this->conn->query($sql,$this->process_data($values));
$self = $this;
while (($row = $sth->fetch()))
{
$cb = function() use ($row, $self)
{
return new $self->class->name($row, false, true, false);
};
if ($this->cache_individual_model)
{
$key = $this->cache_key_for_model(array_intersect_key($row, array_flip($this->pk)));
$model = Cache::get($key, $cb, $this->cache_model_expire);
}
else
{
$model = $cb();
}
if ($readonly)
$model->readonly();
if ($collect_attrs_for_includes)
$attrs[] = $model->attributes();
$list[] = $model;
}
if ($collect_attrs_for_includes && !empty($list))
$this->execute_eager_load($list, $attrs, $includes);
return $list;
}
/**
* Executes an eager load of a given named relationship for this table.
*
* @param $models array found modesl for this table
* @param $attrs array of attrs from $models
* @param $includes array eager load directives
* @return void
*/
private function execute_eager_load($models=array(), $attrs=array(), $includes=array())
{
if (!is_array($includes))
$includes = array($includes);
foreach ($includes as $index => $name)
{
// nested include
if (is_array($name))
{
$nested_includes = count($name) > 0 ? $name : array();
$name = $index;
}
else
$nested_includes = array();
$rel = $this->get_relationship($name, true);
$rel->load_eagerly($models, $attrs, $nested_includes, $this);
}
}
public function get_column_by_inflected_name($inflected_name)
{
foreach ($this->columns as $raw_name => $column)
{
if ($column->inflected_name == $inflected_name)
return $column;
}
return null;
}
public function get_fully_qualified_table_name($quote_name=true)
{
$table = $quote_name ? $this->conn->quote_name($this->table) : $this->table;
if ($this->db_name)
$table = $this->conn->quote_name($this->db_name) . ".$table";
return $table;
}
/**
* Retrieve a relationship object for this table. Strict as true will throw an error
* if the relationship name does not exist.
*
* @param $name string name of Relationship
* @param $strict bool
* @throws RelationshipException
* @return HasOne|HasMany|BelongsTo Relationship or null
*/
public function get_relationship($name, $strict=false)
{
if ($this->has_relationship($name))
return $this->relationships[$name];
if ($strict)
throw new RelationshipException("Relationship named $name has not been declared for class: {$this->class->getName()}");
return null;
}
/**
* Does a given relationship exist?
*
* @param $name string name of Relationship
* @return bool
*/
public function has_relationship($name)
{
return array_key_exists($name, $this->relationships);
}
public function insert(&$data, $pk=null, $sequence_name=null)
{
$data = $this->process_data($data);
$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
$sql->insert($data,$pk,$sequence_name);
$values = array_values($data);
return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
}
public function update(&$data, $where)
{
$data = $this->process_data($data);
$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
$sql->update($data)->where($where);
$values = $sql->bind_values();
return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
}
public function delete($data)
{
$data = $this->process_data($data);
$sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
$sql->delete($data);
$values = $sql->bind_values();
return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
}
/**
* Add a relationship.
*
* @param Relationship $relationship a Relationship object
*/
private function add_relationship($relationship)
{
$this->relationships[$relationship->attribute_name] = $relationship;
}
private function get_meta_data()
{
// as more adapters are added probably want to do this a better way
// than using instanceof but gud enuff for now
$quote_name = !($this->conn instanceof PgsqlAdapter);
$table_name = $this->get_fully_qualified_table_name($quote_name);
$conn = $this->conn;
$this->columns = Cache::get("get_meta_data-$table_name", function() use ($conn, $table_name) { return $conn->columns($table_name); });
}
/**
* Replaces any aliases used in a hash based condition.
*
* @param $hash array A hash
* @param $map array Hash of used_name => real_name
* @return array Array with any aliases replaced with their read field name
*/
private function map_names(&$hash, &$map)
{
$ret = array();
foreach ($hash as $name => &$value)
{
if (array_key_exists($name,$map))
$name = $map[$name];
$ret[$name] = $value;
}
return $ret;
}
private function &process_data($hash)
{
if (!$hash)
return $hash;
$date_class = Config::instance()->get_date_class();
foreach ($hash as $name => &$value)
{
if ($value instanceof $date_class || $value instanceof \DateTime)
{
if (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE)
$hash[$name] = $this->conn->date_to_string($value);
else
$hash[$name] = $this->conn->datetime_to_string($value);
}
else
$hash[$name] = $value;
}
return $hash;
}
private function set_primary_key()
{
if (($pk = $this->class->getStaticPropertyValue('pk',null)) || ($pk = $this->class->getStaticPropertyValue('primary_key',null)))
$this->pk = is_array($pk) ? $pk : array($pk);
else
{
$this->pk = array();
foreach ($this->columns as $c)
{
if ($c->pk)
$this->pk[] = $c->inflected_name;
}
}
}
private function set_table_name()
{
if (($table = $this->class->getStaticPropertyValue('table',null)) || ($table = $this->class->getStaticPropertyValue('table_name',null)))
$this->table = $table;
else
{
// infer table name from the class name
$this->table = Inflector::instance()->tableize($this->class->getName());
// strip namespaces from the table name if any
$parts = explode('\\',$this->table);
$this->table = $parts[count($parts)-1];
}
if (($db = $this->class->getStaticPropertyValue('db',null)) || ($db = $this->class->getStaticPropertyValue('db_name',null)))
$this->db_name = $db;
}
private function set_cache()
{
if (!Cache::$adapter)
return;
$model_class_name = $this->class->name;
$this->cache_individual_model = $model_class_name::$cache;
if (property_exists($model_class_name, 'cache_expire') && isset($model_class_name::$cache_expire))
{
$this->cache_model_expire = $model_class_name::$cache_expire;
}
else
{
$this->cache_model_expire = Cache::$options['expire'];
}
}
private function set_sequence_name()
{
if (!$this->conn->supports_sequences())
return;
if (!($this->sequence = $this->class->getStaticPropertyValue('sequence')))
$this->sequence = $this->conn->get_sequence_name($this->table,$this->pk[0]);
}
private function set_associations()
{
require_once __DIR__ . '/Relationship.php';
$namespace = $this->class->getNamespaceName();
foreach ($this->class->getStaticProperties() as $name => $definitions)
{
if (!$definitions)# || !is_array($definitions))
continue;
foreach (wrap_strings_in_arrays($definitions) as $definition)
{
$relationship = null;
$definition += array('namespace' => $namespace);
switch ($name)
{
case 'has_many':
$relationship = new HasMany($definition);
break;
case 'has_one':
$relationship = new HasOne($definition);
break;
case 'belongs_to':
$relationship = new BelongsTo($definition);
break;
case 'has_and_belongs_to_many':
$relationship = new HasAndBelongsToMany($definition);
break;
}
if ($relationship)
$this->add_relationship($relationship);
}
}
}
/**
* Rebuild the delegates array into format that we can more easily work with in Model.
* Will end up consisting of array of:
*
* array('delegate' => array('field1','field2',...),
* 'to' => 'delegate_to_relationship',
* 'prefix' => 'prefix')
*/
private function set_delegates()
{
$delegates = $this->class->getStaticPropertyValue('delegate',array());
$new = array();
if (!array_key_exists('processed', $delegates))
$delegates['processed'] = false;
if (!empty($delegates) && !$delegates['processed'])
{
foreach ($delegates as &$delegate)
{
if (!is_array($delegate) || !isset($delegate['to']))
continue;
if (!isset($delegate['prefix']))
$delegate['prefix'] = null;
$new_delegate = array(
'to' => $delegate['to'],
'prefix' => $delegate['prefix'],
'delegate' => array());
foreach ($delegate as $name => $value)
{
if (is_numeric($name))
$new_delegate['delegate'][] = $value;
}
$new[] = $new_delegate;
}
$new['processed'] = true;
$this->class->setStaticPropertyValue('delegate',$new);
}
}
/**
* @deprecated Model.php now checks for get|set_ methods via method_exists so there is no need for declaring static g|setters.
*/
private function set_setters_and_getters()
{
$getters = $this->class->getStaticPropertyValue('getters', array());
$setters = $this->class->getStaticPropertyValue('setters', array());
if (!empty($getters) || !empty($setters))
trigger_error('static::$getters and static::$setters are deprecated. Please define your setters and getters by declaring methods in your model prefixed with get_ or set_. See
http://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-setters and http://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-getters on how to make use of this option.', E_USER_DEPRECATED);
}
}
================================================
FILE: lib/Utils.php
================================================
<?php
/**
*
* @package ActiveRecord
*/
/*
* Thanks to http://www.eval.ca/articles/php-pluralize (MIT license)
* http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/inflections.rb (MIT license)
* http://www.fortunecity.com/bally/durrus/153/gramch13.html
* http://www2.gsu.edu/~wwwesl/egw/crump.htm
*
* Changes (12/17/07)
* Major changes
* --
* Fixed irregular noun algorithm to use regular expressions just like the original Ruby source.
* (this allows for things like fireman -> firemen
* Fixed the order of the singular array, which was backwards.
*
* Minor changes
* --
* Removed incorrect pluralization rule for /([^aeiouy]|qu)ies$/ => $1y
* Expanded on the list of exceptions for *o -> *oes, and removed rule for buffalo -> buffaloes
* Removed dangerous singularization rule for /([^f])ves$/ => $1fe
* Added more specific rules for singularizing lives, wives, knives, sheaves, loaves, and leaves and thieves
* Added exception to /(us)es$/ => $1 rule for houses => house and blouses => blouse
* Added excpetions for feet, geese and teeth
* Added rule for deer -> deer
*
* Changes:
* Removed rule for virus -> viri
* Added rule for potato -> potatoes
* Added rule for *us -> *uses
*/
namespace ActiveRecord;
use \Closure;
function classify($class_name, $singularize=false)
{
if ($singularize)
$class_name = Utils::singularize($class_name);
$class_name = Inflector::instance()->camelize($class_name);
return ucfirst($class_name);
}
// http://snippets.dzone.com/posts/show/4660
function array_flatten(array $array)
{
$i = 0;
while ($i < count($array))
{
if (is_array($array[$i]))
array_splice($array,$i,1,$array[$i]);
else
++$i;
}
return $array;
}
/**
* Somewhat naive way to determine if an array is a hash.
*/
function is_hash(&$array)
{
if (!is_array($array))
return false;
$keys = array_keys($array);
return @is_string($keys[0]) ? true : false;
}
/**
* Strips a class name of any namespaces and namespace operator.
*
* @param string $class
* @return string stripped class name
* @access public
*/
function denamespace($class_name)
{
if (is_object($class_name))
$class_name = get_class($class_name);
if (has_namespace($class_name))
{
$parts = explode('\\', $class_name);
return end($parts);
}
return $class_name;
}
function get_namespaces($class_name)
{
if (has_namespace($class_name))
return explode('\\', $class_name);
return null;
}
function has_namespace($class_name)
{
if (strpos($class_name, '\\') !== false)
return true;
return false;
}
function has_absolute_namespace($class_name)
{
if (strpos($class_name, '\\') === 0)
return true;
return false;
}
/**
* Returns true if all values in $haystack === $needle
* @param $needle
* @param $haystack
* @return unknown_type
*/
function all($needle, array $haystack)
{
foreach ($haystack as $value)
{
if ($value !== $needle)
return false;
}
return true;
}
function collect(&$enumerable, $name_or_closure)
{
$ret = array();
foreach ($enumerable as $value)
{
if (is_string($name_or_closure))
$ret[] = is_array($value) ? $value[$name_or_closure] : $value->$name_or_closure;
elseif ($name_or_closure instanceof Closure)
$ret[] = $name_or_closure($value);
}
return $ret;
}
/**
* Wrap string definitions (if any) into arrays.
*/
function wrap_strings_in_arrays(&$strings)
{
if (!is_array($strings))
$strings = array(array($strings));
else
{
foreach ($strings as &$str)
{
if (!is_array($str))
$str = array($str);
}
}
return $strings;
}
/**
* Some internal utility functions.
*
* @package ActiveRecord
*/
class Utils
{
public static function extract_options($options)
{
return is_array(end($options)) ? end($options) : array();
}
public static function add_condition(&$conditions=array(), $condition, $conjuction='AND')
{
if (is_array($condition))
{
if (empty($conditions))
$conditions = array_flatten($condition);
else
{
$conditions[0] .= " $conjuction " . array_shift($condition);
$conditions[] = array_flatten($condition);
}
}
elseif (is_string($condition))
$conditions[0] .= " $conjuction $condition";
return $conditions;
}
public static function human_attribute($attr)
{
$inflector = Inflector::instance();
$inflected = $inflector->variablize($attr);
$normal = $inflector->uncamelize($inflected);
return ucfirst(str_replace('_', ' ', $normal));
}
public static function is_odd($number)
{
return $number & 1;
}
public static function is_a($type, $var)
{
switch($type)
{
case 'range':
if (is_array($var) && (int)$var[0] < (int)$var[1])
return true;
}
return false;
}
public static function is_blank($var)
{
return 0 === strlen($var);
}
private static $plural = array(
'/(quiz)$/i' => "$1zes",
'/^(ox)$/i' => "$1en",
'/([m|l])ouse$/i' => "$1ice",
'/(matr|vert|ind)ix|ex$/i' => "$1ices",
'/(x|ch|ss|sh)$/i' => "$1es",
'/([^aeiouy]|qu)y$/i' => "$1ies",
'/(hive)$/i' => "$1s",
'/(?:([^f])fe|([lr])f)$/i' => "$1$2ves",
'/(shea|lea|loa|thie)f$/i' => "$1ves",
'/sis$/i' => "ses",
'/([ti])um$/i' => "$1a",
'/(tomat|potat|ech|her|vet)o$/i'=> "$1oes",
'/(bu)s$/i' => "$1ses",
'/(alias)$/i' => "$1es",
'/(octop)us$/i' => "$1i",
'/(cris|ax|test)is$/i' => "$1es",
'/(us)$/i' => "$1es",
'/s$/i' => "s",
'/$/' => "s"
);
private static $singular = array(
'/(quiz)zes$/i' => "$1",
'/(matr)ices$/i' => "$1ix",
'/(vert|ind)ices$/i' => "$1ex",
'/^(ox)en$/i' => "$1",
'/(alias)es$/i' => "$1",
'/(octop|vir)i$/i' => "$1us",
'/(cris|ax|test)es$/i' => "$1is",
'/(shoe)s$/i' => "$1",
'/(o)es$/i' => "$1",
'/(bus)es$/i' => "$1",
'/([m|l])ice$/i' => "$1ouse",
'/(x|ch|ss|sh)es$/i' => "$1",
'/(m)ovies$/i' => "$1ovie",
'/(s)eries$/i' => "$1eries",
'/([^aeiouy]|qu)ies$/i' => "$1y",
'/([lr])ves$/i' => "$1f",
'/(tive)s$/i' => "$1",
'/(hive)s$/i' => "$1",
'/(li|wi|kni)ves$/i' => "$1fe",
'/(shea|loa|lea|thie)ves$/i'=> "$1f",
'/(^analy)ses$/i' => "$1sis",
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => "$1$2sis",
'/([ti])a$/i' => "$1um",
'/(n)ews$/i' => "$1ews",
'/(h|bl)ouses$/i' => "$1ouse",
'/(corpse)s$/i' => "$1",
'/(us)es$/i' => "$1",
'/(us|ss)$/i' => "$1",
'/s$/i' => ""
);
private static $irregular = array(
'move' => 'moves',
'foot' => 'feet',
'goose' => 'geese',
'sex' => 'sexes',
'child' => 'children',
'man' => 'men',
'tooth' => 'teeth',
'person' => 'people'
);
private static $uncountable = array(
'sheep',
'fish',
'deer',
'series',
'species',
'money',
'rice',
'information',
'equipment'
);
public static function pluralize( $string )
{
// save some time in the case that singular and plural are the same
if ( in_array( strtolower( $string ), self::$uncountable ) )
return $string;
// check for irregular singular forms
foreach ( self::$irregular as $pattern => $result )
{
$pattern = '/' . $pattern . '$/i';
if ( preg_match( $pattern, $string ) )
return preg_replace( $pattern, $result, $string);
}
// check for matches using regular expressions
foreach ( self::$plural as $pattern => $result )
{
if ( preg_match( $pattern, $string ) )
return preg_replace( $pattern, $result, $string );
}
return $string;
}
public static function singularize( $string )
{
// save some time in the case that singular and plural are the same
if ( in_array( strtolower( $string ), self::$uncountable ) )
return $string;
// check for irregular plural forms
foreach ( self::$irregular as $result => $pattern )
{
$pattern = '/' . $pattern . '$/i';
if ( preg_match( $pattern, $string ) )
return preg_replace( $pattern, $result, $string);
}
// check for matches using regular expressions
foreach ( self::$singular as $pattern => $result )
{
if ( preg_match( $pattern, $string ) )
return preg_replace( $pattern, $result, $string );
}
return $string;
}
public static function pluralize_if($count, $string)
{
if ($count == 1)
return $string;
else
return self::pluralize($string);
}
public static function squeeze($char, $string)
{
return preg_replace("/$char+/",$char,$string);
}
public static function add_irregular($singular, $plural)
{
self::$irregular[$singular] = $plural;
}
}
================================================
FILE: lib/Validations.php
================================================
<?php
/**
* These two classes have been <i>heavily borrowed</i> from Ruby on Rails' ActiveRecord so much that
* this piece can be considered a straight port. The reason for this is that the vaildation process is
* tricky due to order of operations/events. The former combined with PHP's odd typecasting means
* that it was easier to formulate this piece base on the rails code.
*
* @package ActiveRecord
*/
namespace ActiveRecord;
use ActiveRecord\Model;
use IteratorAggregate;
use ArrayIterator;
/**
* Manages validations for a {@link Model}.
*
* This class isn't meant to be directly used. Instead you define
* validators thru static variables in your {@link Model}. Example:
*
* <code>
* class Person extends ActiveRecord\Model {
* static $validates_length_of = array(
* array('name', 'within' => array(30,100),
* array('state', 'is' => 2)
* );
* }
*
* $person = new Person();
* $person->name = 'Tito';
* $person->state = 'this is not two characters';
*
* if (!$person->is_valid())
* print_r($person->errors);
* </code>
*
* @package ActiveRecord
* @see Errors
* @link http://www.phpactiverecord.org/guides/validations
*/
class Validations
{
private $model;
private $options =
gitextract_w5_8ak3n/
├── .editorconfig
├── .gitignore
├── .travis.yml
├── ActiveRecord.php
├── CHANGELOG
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── examples/
│ ├── orders/
│ │ ├── models/
│ │ │ ├── Order.php
│ │ │ ├── Payment.php
│ │ │ └── Person.php
│ │ ├── orders.php
│ │ └── orders.sql
│ └── simple/
│ ├── simple.php
│ ├── simple.sql
│ ├── simple_with_options.php
│ └── simple_with_options.sql
├── lib/
│ ├── Cache.php
│ ├── CallBack.php
│ ├── Column.php
│ ├── Config.php
│ ├── Connection.php
│ ├── ConnectionManager.php
│ ├── DateTime.php
│ ├── DateTimeInterface.php
│ ├── Exceptions.php
│ ├── Expressions.php
│ ├── Inflector.php
│ ├── Model.php
│ ├── Reflections.php
│ ├── Relationship.php
│ ├── SQLBuilder.php
│ ├── Serialization.php
│ ├── Singleton.php
│ ├── Table.php
│ ├── Utils.php
│ ├── Validations.php
│ ├── adapters/
│ │ ├── MysqlAdapter.php
│ │ ├── OciAdapter.php
│ │ ├── PgsqlAdapter.php
│ │ └── SqliteAdapter.php
│ └── cache/
│ └── Memcache.php
├── phpunit.xml.dist
└── test/
├── ActiveRecordCacheTest.php
├── ActiveRecordFindTest.php
├── ActiveRecordTest.php
├── ActiveRecordWriteTest.php
├── CacheModelTest.php
├── CacheTest.php
├── CallbackTest.php
├── ColumnTest.php
├── ConfigTest.php
├── ConnectionManagerTest.php
├── ConnectionTest.php
├── DateFormatTest.php
├── DateTimeTest.php
├── ExpressionsTest.php
├── HasManyThroughTest.php
├── InflectorTest.php
├── ModelCallbackTest.php
├── MysqlAdapterTest.php
├── OciAdapterTest.php
├── PgsqlAdapterTest.php
├── RelationshipTest.php
├── SQLBuilderTest.php
├── SerializationTest.php
├── SqliteAdapterTest.php
├── UtilsTest.php
├── ValidatesFormatOfTest.php
├── ValidatesInclusionAndExclusionOfTest.php
├── ValidatesLengthOfTest.php
├── ValidatesNumericalityOfTest.php
├── ValidatesPresenceOfTest.php
├── ValidationsTest.php
├── fixtures/
│ ├── amenities.csv
│ ├── authors.csv
│ ├── awesome_people.csv
│ ├── books.csv
│ ├── employees.csv
│ ├── events.csv
│ ├── hosts.csv
│ ├── newsletters.csv
│ ├── positions.csv
│ ├── property.csv
│ ├── property_amenities.csv
│ ├── publishers.csv
│ ├── rm-bldg.csv
│ ├── user_newsletters.csv
│ ├── users.csv
│ ├── valuestore.csv
│ └── venues.csv
├── helpers/
│ ├── AdapterTest.php
│ ├── DatabaseLoader.php
│ ├── DatabaseTest.php
│ ├── SnakeCase_PHPUnit_Framework_TestCase.php
│ ├── config.php
│ └── foo.php
├── models/
│ ├── Amenity.php
│ ├── Author.php
│ ├── AuthorAttrAccessible.php
│ ├── AwesomePerson.php
│ ├── Book.php
│ ├── BookAttrAccessible.php
│ ├── BookAttrProtected.php
│ ├── Employee.php
│ ├── Event.php
│ ├── Host.php
│ ├── JoinAuthor.php
│ ├── JoinBook.php
│ ├── NamespaceTest/
│ │ ├── Book.php
│ │ └── SubNamespaceTest/
│ │ └── Page.php
│ ├── Position.php
│ ├── Property.php
│ ├── PropertyAmenity.php
│ ├── Publisher.php
│ ├── RmBldg.php
│ ├── Venue.php
│ ├── VenueAfterCreate.php
│ └── VenueCB.php
└── sql/
├── mysql.sql
├── oci-after-fixtures.sql
├── oci.sql
├── pgsql-after-fixtures.sql
├── pgsql.sql
└── sqlite.sql
SYMBOL INDEX (1324 symbols across 96 files)
FILE: ActiveRecord.php
function activerecord_autoload (line 30) | function activerecord_autoload($class_name)
FILE: examples/orders/models/Order.php
class Order (line 2) | class Order extends ActiveRecord\Model
method apply_tax (line 25) | public function apply_tax()
FILE: examples/orders/models/Payment.php
class Payment (line 2) | class Payment extends ActiveRecord\Model
FILE: examples/orders/models/Person.php
class Person (line 2) | class Person extends ActiveRecord\Model
FILE: examples/orders/orders.sql
type people (line 4) | create table people(
type orders (line 13) | create table orders(
type payments (line 23) | create table payments(
FILE: examples/simple/simple.php
class Book (line 6) | class Book extends ActiveRecord\Model { }
FILE: examples/simple/simple.sql
type books (line 1) | create table books(
FILE: examples/simple/simple_with_options.php
class Book (line 4) | class Book extends ActiveRecord\Model
FILE: examples/simple/simple_with_options.sql
type simple_book (line 1) | create table simple_book(
FILE: lib/Cache.php
class Cache (line 11) | class Cache
method initialize (line 40) | public static function initialize($url, $options=array())
method flush (line 56) | public static function flush()
method get (line 70) | public static function get($key, $closure, $expire=null)
method set (line 88) | public static function set($key, $var, $expire=null)
method delete (line 102) | public static function delete($key)
method get_namespace (line 111) | private static function get_namespace()
FILE: lib/CallBack.php
class CallBack (line 59) | class CallBack
method __construct (line 110) | public function __construct($model_class_name)
method get_callbacks (line 140) | public function get_callbacks($name)
method invoke (line 159) | public function invoke($model, $name, $must_exist=true)
method register (line 211) | public function register($name, $closure_or_method_name=null, $options...
FILE: lib/Column.php
class Column (line 12) | class Column
method castIntegerSafely (line 122) | public static function castIntegerSafely($value)
method cast (line 153) | public function cast($value, $connection)
method map_raw_type (line 189) | public function map_raw_type()
FILE: lib/Config.php
class Config (line 22) | class Config extends Singleton
method initialize (line 115) | public static function initialize(Closure $initializer)
method set_connections (line 133) | public function set_connections($connections, $default_connection=null)
method get_connections (line 149) | public function get_connections()
method get_connection (line 160) | public function get_connection($name)
method get_default_connection_string (line 173) | public function get_default_connection_string()
method get_default_connection (line 184) | public function get_default_connection()
method set_default_connection (line 195) | public function set_default_connection($name)
method set_model_directory (line 206) | public function set_model_directory($dir)
method get_model_directory (line 217) | public function get_model_directory()
method set_logging (line 231) | public function set_logging($bool)
method set_logger (line 243) | public function set_logger($logger)
method get_logging (line 258) | public function get_logging()
method get_logger (line 268) | public function get_logger()
method set_date_class (line 273) | public function set_date_class($date_class)
method get_date_class (line 290) | public function get_date_class()
method get_date_format (line 298) | public function get_date_format()
method set_date_format (line 307) | public function set_date_format($format)
method set_cache (line 329) | public function set_cache($url, $options=array())
FILE: lib/Connection.php
class Connection (line 20) | abstract class Connection
method instance (line 102) | public static function instance($connection_string_or_connection_name=...
method load_adapter_class (line 141) | private static function load_adapter_class($adapter)
method parse_connection_url (line 176) | public static function parse_connection_url($connection_url)
method __construct (line 246) | protected function __construct($info)
method columns (line 272) | public function columns($table)
method escape (line 290) | public function escape($string)
method insert_id (line 301) | public function insert_id($sequence=null)
method query (line 313) | public function query($sql, &$values=array())
method query_and_fetch_one (line 348) | public function query_and_fetch_one($sql, &$values=array())
method query_and_fetch (line 361) | public function query_and_fetch($sql, Closure $handler)
method tables (line 374) | public function tables()
method transaction (line 388) | public function transaction()
method commit (line 397) | public function commit()
method rollback (line 406) | public function rollback()
method supports_sequences (line 417) | function supports_sequences()
method get_sequence_name (line 429) | public function get_sequence_name($table, $column_name)
method next_sequence_value (line 440) | public function next_sequence_value($sequence_name)
method quote_name (line 451) | public function quote_name($string)
method date_to_string (line 463) | public function date_to_string($datetime)
method datetime_to_string (line 474) | public function datetime_to_string($datetime)
method string_to_datetime (line 485) | public function string_to_datetime($string)
method limit (line 510) | abstract function limit($sql, $offset, $limit);
method query_column_info (line 518) | abstract public function query_column_info($table);
method query_for_tables (line 526) | abstract function query_for_tables();
method set_encoding (line 531) | abstract function set_encoding($charset);
method native_database_types (line 537) | abstract public function native_database_types();
method accepts_limit_and_order_for_update_and_delete (line 545) | public function accepts_limit_and_order_for_update_and_delete()
FILE: lib/ConnectionManager.php
class ConnectionManager (line 12) | class ConnectionManager extends Singleton
method get_connection (line 27) | public static function get_connection($name=null)
method drop_connection (line 46) | public static function drop_connection($name=null)
FILE: lib/DateTime.php
class DateTime (line 36) | class DateTime extends \DateTime implements DateTimeInterface
method attribute_of (line 67) | public function attribute_of($model, $attribute_name)
method format (line 87) | public function format($format=null)
method get_format (line 102) | public static function get_format($format=null)
method createFromFormat (line 120) | public static function createFromFormat($format, $time, $tz = null)
method __toString (line 131) | public function __toString()
method __clone (line 144) | public function __clone()
method flag_dirty (line 150) | private function flag_dirty()
method setDate (line 156) | public function setDate($year, $month, $day)
method setISODate (line 162) | public function setISODate($year, $week , $day = 1)
method setTime (line 168) | public function setTime($hour, $minute, $second = 0, $microseconds = 0)
method setTimestamp (line 174) | public function setTimestamp($unixtimestamp)
method setTimezone (line 180) | public function setTimezone($timezone)
method modify (line 186) | public function modify($modify)
method add (line 192) | public function add($interval)
method sub (line 198) | public function sub($interval)
FILE: lib/DateTimeInterface.php
type DateTimeInterface (line 15) | interface DateTimeInterface
method attribute_of (line 24) | public function attribute_of($model, $attribute_name);
method format (line 29) | public function format($format=null);
method createFromFormat (line 34) | public static function createFromFormat($format, $time, $tz = null);
FILE: lib/Exceptions.php
class ActiveRecordException (line 12) | class ActiveRecordException extends \Exception {}
class RecordNotFound (line 19) | class RecordNotFound extends ActiveRecordException {}
class DatabaseException (line 28) | class DatabaseException extends ActiveRecordException
method __construct (line 30) | public function __construct($adapter_or_string_or_mystery)
class ModelException (line 54) | class ModelException extends ActiveRecordException {}
class ExpressionsException (line 61) | class ExpressionsException extends ActiveRecordException {}
class ConfigException (line 68) | class ConfigException extends ActiveRecordException {}
class CacheException (line 75) | class CacheException extends ActiveRecordException {}
class UndefinedPropertyException (line 82) | class UndefinedPropertyException extends ModelException
method __construct (line 90) | public function __construct($class_name, $property_name)
class ReadOnlyException (line 108) | class ReadOnlyException extends ModelException
method __construct (line 117) | public function __construct($class_name, $method_name)
class ValidationsArgumentError (line 129) | class ValidationsArgumentError extends ActiveRecordException {}
class RelationshipException (line 136) | class RelationshipException extends ActiveRecordException {}
class HasManyThroughAssociationException (line 143) | class HasManyThroughAssociationException extends RelationshipException {}
FILE: lib/Expressions.php
class Expressions (line 17) | class Expressions
method __construct (line 25) | public function __construct($connection, $expressions=null /* [, $valu...
method bind (line 50) | public function bind($parameter_number, $value)
method bind_values (line 58) | public function bind_values($values)
method values (line 66) | public function values()
method get_connection (line 74) | public function get_connection()
method set_connection (line 85) | public function set_connection($connection)
method to_s (line 90) | public function to_s($substitute=false, &$options=null)
method build_sql_from_hash (line 124) | private function build_sql_from_hash(&$hash, $glue)
method substitute (line 145) | private function substitute(&$values, $substitute, $pos, $parameter_in...
method stringify_value (line 177) | private function stringify_value($value)
method quote_string (line 185) | private function quote_string($value)
FILE: lib/Inflector.php
class Inflector (line 10) | abstract class Inflector
method instance (line 17) | public static function instance()
method camelize (line 28) | public function camelize($s)
method is_upper (line 57) | public static function is_upper($s)
method is_lower (line 68) | public static function is_lower($s)
method uncamelize (line 79) | public function uncamelize($s)
method underscorify (line 99) | public function underscorify($s)
method keyify (line 104) | public function keyify($class_name)
method variablize (line 109) | abstract function variablize($s);
class StandardInflector (line 115) | class StandardInflector extends Inflector
method tableize (line 117) | public function tableize($s) { return Utils::pluralize(strtolower($thi...
method variablize (line 118) | public function variablize($s) { return str_replace(array('-',' '),arr...
FILE: lib/Model.php
class Model (line 74) | class Model
method __construct (line 264) | public function __construct(array $attributes=array(), $guard_attribut...
method __get (line 337) | public function &__get($name)
method __isset (line 356) | public function __isset($attribute_name)
method __set (line 411) | public function __set($name, $value)
method __wakeup (line 437) | public function __wakeup()
method assign_attribute (line 450) | public function assign_attribute($name, $value)
method read_attribute (line 494) | public function &read_attribute($name)
method flag_dirty (line 550) | public function flag_dirty($name)
method dirty_attributes (line 563) | public function dirty_attributes()
method attribute_is_dirty (line 577) | public function attribute_is_dirty($attribute)
method attributes (line 587) | public function attributes()
method get_primary_key (line 598) | public function get_primary_key($first=false)
method get_real_attribute_name (line 610) | public function get_real_attribute_name($name)
method get_validation_rules (line 639) | public function get_validation_rules()
method get_values_for (line 653) | public function get_values_for($attributes)
method table_name (line 670) | public static function table_name()
method is_delegated (line 683) | private function is_delegated($name, &$delegate)
method is_readonly (line 699) | public function is_readonly()
method is_new_record (line 709) | public function is_new_record()
method verify_not_readonly (line 720) | private function verify_not_readonly($method_name)
method readonly (line 731) | public function readonly($readonly=true)
method connection (line 741) | public static function connection()
method reestablish_connection (line 751) | public static function reestablish_connection()
method table (line 763) | public static function table()
method create (line 776) | public static function create($attributes, $validate=true, $guard_attr...
method save (line 796) | public function save($validate=true)
method insert (line 809) | private function insert($validate=true)
method update (line 869) | private function update($validate=true)
method expire_cache (line 895) | protected function expire_cache()
method cache_key (line 904) | protected function cache_key()
method delete_all (line 944) | public static function delete_all($options=array())
method update_all (line 997) | public static function update_all($options=array())
method delete (line 1030) | public function delete()
method values_for_pk (line 1054) | public function values_for_pk()
method values_for (line 1065) | public function values_for($attribute_names)
method _validate (line 1080) | private function _validate()
method is_dirty (line 1111) | public function is_dirty()
method is_valid (line 1122) | public function is_valid()
method is_invalid (line 1133) | public function is_invalid()
method set_timestamps (line 1141) | public function set_timestamps()
method update_attributes (line 1158) | public function update_attributes($attributes)
method update_attribute (line 1171) | public function update_attribute($name, $value)
method set_attributes (line 1186) | public function set_attributes(array $attributes)
method set_attributes_via_mass_assignment (line 1198) | private function set_attributes_via_mass_assignment(array &$attributes...
method set_relationship_from_eager_load (line 1254) | public function set_relationship_from_eager_load(Model $model=null, $n...
method reload (line 1280) | public function reload()
method __clone (line 1292) | public function __clone()
method reset_dirty (line 1304) | public function reset_dirty()
method __callStatic (line 1355) | public static function __callStatic($method, $args)
method __call (line 1403) | public function __call($method, $args)
method all (line 1434) | public static function all(/* ... */)
method count (line 1449) | public static function count(/* ... */)
method exists (line 1481) | public static function exists(/* ... */)
method first (line 1492) | public static function first(/* ... */)
method last (line 1503) | public static function last(/* ... */)
method find (line 1562) | public static function find(/* $type, $options */)
method get_models_from_cache (line 1619) | protected static function get_models_from_cache($pks, $options)
method find_by_pk (line 1650) | public static function find_by_pk($values, $options)
method find_by_sql (line 1698) | public static function find_by_sql($sql, $values=null)
method query (line 1710) | public static function query($sql, $values=null)
method is_options_hash (line 1723) | public static function is_options_hash($array, $throw=true)
method pk_conditions (line 1748) | public static function pk_conditions($args)
method extract_and_validate_options (line 1762) | public static function extract_and_validate_options(array &$array)
method to_json (line 1796) | public function to_json(array $options=array())
method to_xml (line 1808) | public function to_xml(array $options=array())
method to_csv (line 1833) | public function to_csv(array $options=array())
method to_array (line 1845) | public function to_array(array $options=array())
method serialize (line 1867) | private function serialize($type, $options)
method invoke_callback (line 1882) | private function invoke_callback($method_name, $must_exist=true)
method transaction (line 1918) | public static function transaction($closure)
FILE: lib/Reflections.php
class Reflections (line 13) | class Reflections extends Singleton
method add (line 28) | public function add($class=null)
method destroy (line 46) | public function destroy($class)
method get (line 59) | public function get($class=null)
method get_class (line 75) | private function get_class($mixed=null)
FILE: lib/Relationship.php
type InterfaceRelationship (line 12) | interface InterfaceRelationship
method __construct (line 14) | public function __construct($options=array());
method build_association (line 15) | public function build_association(Model $model, $attributes=array(), $...
method create_association (line 16) | public function create_association(Model $model, $attributes=array(), ...
class AbstractRelationship (line 25) | abstract class AbstractRelationship implements InterfaceRelationship
method __construct (line 75) | public function __construct($options=array())
method get_table (line 99) | protected function get_table()
method is_poly (line 109) | public function is_poly()
method query_and_attach_related_models_eagerly (line 129) | protected function query_and_attach_related_models_eagerly(Table $tabl...
method build_association (line 222) | public function build_association(Model $model, $attributes=array(), $...
method create_association (line 235) | public function create_association(Model $model, $attributes=array(), ...
method append_record_to_associate (line 242) | protected function append_record_to_associate(Model $associate, Model ...
method merge_association_options (line 254) | protected function merge_association_options($options)
method unset_non_finder_options (line 265) | protected function unset_non_finder_options($options)
method set_inferred_class_name (line 283) | protected function set_inferred_class_name()
method set_class_name (line 289) | protected function set_class_name($class_name)
method create_conditions_from_keys (line 303) | protected function create_conditions_from_keys(Model $model, $conditio...
method construct_inner_join_sql (line 331) | public function construct_inner_join_sql(Table $from_table, $using_thr...
method load (line 384) | abstract function load(Model $model);
class HasMany (line 429) | class HasMany extends AbstractRelationship
method __construct (line 457) | public function __construct($options=array())
method set_keys (line 476) | protected function set_keys($model_class_name, $override=false)
method load (line 486) | public function load(Model $model)
method get_foreign_key_for_new_association (line 539) | private function get_foreign_key_for_new_association(Model $model)
method inject_foreign_key_for_new_association (line 549) | private function inject_foreign_key_for_new_association(Model $model, ...
method build_association (line 559) | public function build_association(Model $model, $attributes=array(), $...
method create_association (line 580) | public function create_association(Model $model, $attributes=array(), ...
method load_eagerly (line 604) | public function load_eagerly($models=array(), $attributes=array(), $in...
class HasOne (line 629) | class HasOne extends HasMany
class HasAndBelongsToMany (line 638) | class HasAndBelongsToMany extends AbstractRelationship
method __construct (line 640) | public function __construct($options=array())
method load (line 651) | public function load(Model $model)
class BelongsTo (line 686) | class BelongsTo extends AbstractRelationship
method __construct (line 688) | public function __construct($options=array())
method __get (line 700) | public function __get($name)
method load (line 709) | public function load(Model $model)
method load_eagerly (line 726) | public function load_eagerly($models=array(), $attributes, $includes, ...
FILE: lib/SQLBuilder.php
class SQLBuilder (line 12) | class SQLBuilder
method __construct (line 42) | public function __construct($connection, $table)
method __toString (line 56) | public function __toString()
method to_s (line 67) | public function to_s()
method bind_values (line 78) | public function bind_values()
method get_where_values (line 91) | public function get_where_values()
method where (line 96) | public function where(/* (conditions, values) || (hash) */)
method order (line 102) | public function order($order)
method group (line 108) | public function group($group)
method having (line 114) | public function having($having)
method limit (line 120) | public function limit($limit)
method offset (line 126) | public function offset($offset)
method select (line 132) | public function select($select)
method joins (line 139) | public function joins($joins)
method insert (line 145) | public function insert($hash, $pk=null, $sequence_name=null)
method update (line 159) | public function update($mixed)
method delete (line 173) | public function delete()
method reverse_order (line 183) | public static function reverse_order($order)
method create_conditions_from_underscored_string (line 214) | public static function create_conditions_from_underscored_string(Conne...
method create_hash_from_underscored_string (line 257) | public static function create_hash_from_underscored_string($name, &$va...
method prepend_table_name_to_fields (line 278) | private function prepend_table_name_to_fields($hash=array())
method apply_where_conditions (line 292) | private function apply_where_conditions($args)
method build_delete (line 327) | private function build_delete()
method build_insert (line 346) | private function build_insert()
method build_select (line 364) | private function build_select()
method build_update (line 389) | private function build_update()
method quoted_key_names (line 413) | private function quoted_key_names()
FILE: lib/Serialization.php
class Serialization (line 43) | abstract class Serialization
method __construct (line 96) | public function __construct(Model $model, &$options)
method parse_options (line 104) | private function parse_options()
method check_only (line 113) | private function check_only()
method check_except (line 124) | private function check_except()
method check_methods (line 133) | private function check_methods()
method check_only_method (line 147) | private function check_only_method()
method check_include (line 157) | private function check_include()
method options_to_a (line 209) | final protected function options_to_a($key)
method to_a (line 219) | final public function to_a()
method __toString (line 235) | final public function __toString()
method to_s (line 244) | abstract public function to_s();
class ArraySerializer (line 252) | class ArraySerializer extends Serialization
method to_s (line 256) | public function to_s()
class JsonSerializer (line 267) | class JsonSerializer extends ArraySerializer
method to_s (line 271) | public function to_s()
class XmlSerializer (line 283) | class XmlSerializer extends Serialization
method __construct (line 287) | public function __construct(Model $model, &$options)
method to_s (line 293) | public function to_s()
method xml_encode (line 298) | private function xml_encode()
method write (line 315) | private function write($data, $tag=null)
class CsvSerializer (line 346) | class CsvSerializer extends Serialization
method to_s (line 351) | public function to_s()
method header (line 357) | private function header()
method row (line 362) | private function row()
method to_csv (line 367) | private function to_csv($arr)
FILE: lib/Singleton.php
class Singleton (line 15) | abstract class Singleton
method instance (line 29) | final public static function instance()
method __clone (line 44) | final private function __clone() {}
method get_called_class (line 51) | final protected function get_called_class()
FILE: lib/Table.php
class Table (line 16) | class Table
method load (line 65) | public static function load($model_class_name)
method clear_cache (line 78) | public static function clear_cache($model_class_name=null)
method __construct (line 86) | public function __construct($class_name)
method reestablish_connection (line 104) | public function reestablish_connection($close=true)
method create_joins (line 117) | public function create_joins($joins)
method options_to_sql (line 160) | public function options_to_sql($options)
method find (line 213) | public function find($options)
method cache_key_for_model (line 222) | public function cache_key_for_model($pk)
method find_by_sql (line 231) | public function find_by_sql($sql, $values=null, $readonly=false, $incl...
method execute_eager_load (line 279) | private function execute_eager_load($models=array(), $attrs=array(), $...
method get_column_by_inflected_name (line 300) | public function get_column_by_inflected_name($inflected_name)
method get_fully_qualified_table_name (line 310) | public function get_fully_qualified_table_name($quote_name=true)
method get_relationship (line 329) | public function get_relationship($name, $strict=false)
method has_relationship (line 346) | public function has_relationship($name)
method insert (line 351) | public function insert(&$data, $pk=null, $sequence_name=null)
method update (line 362) | public function update(&$data, $where)
method delete (line 373) | public function delete($data)
method add_relationship (line 389) | private function add_relationship($relationship)
method get_meta_data (line 394) | private function get_meta_data()
method map_names (line 412) | private function map_names(&$hash, &$map)
method process_data (line 426) | private function &process_data($hash)
method set_primary_key (line 447) | private function set_primary_key()
method set_table_name (line 463) | private function set_table_name()
method set_cache (line 481) | private function set_cache()
method set_sequence_name (line 498) | private function set_sequence_name()
method set_associations (line 507) | private function set_associations()
method set_delegates (line 555) | private function set_delegates()
method set_setters_and_getters (line 595) | private function set_setters_and_getters()
FILE: lib/Utils.php
function classify (line 39) | function classify($class_name, $singularize=false)
function array_flatten (line 49) | function array_flatten(array $array)
function is_hash (line 66) | function is_hash(&$array)
function denamespace (line 82) | function denamespace($class_name)
function get_namespaces (line 95) | function get_namespaces($class_name)
function has_namespace (line 102) | function has_namespace($class_name)
function has_absolute_namespace (line 109) | function has_absolute_namespace($class_name)
function all (line 122) | function all($needle, array $haystack)
function collect (line 132) | function collect(&$enumerable, $name_or_closure)
function wrap_strings_in_arrays (line 149) | function wrap_strings_in_arrays(&$strings)
class Utils (line 169) | class Utils
method extract_options (line 171) | public static function extract_options($options)
method add_condition (line 176) | public static function add_condition(&$conditions=array(), $condition,...
method human_attribute (line 194) | public static function human_attribute($attr)
method is_odd (line 203) | public static function is_odd($number)
method is_a (line 208) | public static function is_a($type, $var)
method is_blank (line 221) | public static function is_blank($var)
method pluralize (line 303) | public static function pluralize( $string )
method singularize (line 328) | public static function singularize( $string )
method pluralize_if (line 353) | public static function pluralize_if($count, $string)
method squeeze (line 361) | public static function squeeze($char, $string)
method add_irregular (line 366) | public static function add_irregular($singular, $plural)
FILE: lib/Validations.php
class Validations (line 42) | class Validations
method __construct (line 91) | public function __construct(Model $model)
method get_record (line 99) | public function get_record()
method rules (line 109) | public function rules()
method validate (line 136) | public function validate()
method validates_presence_of (line 175) | public function validates_presence_of($attrs)
method validates_inclusion_of (line 208) | public function validates_inclusion_of($attrs)
method validates_exclusion_of (line 228) | public function validates_exclusion_of($attrs)
method validates_inclusion_or_exclusion_of (line 250) | public function validates_inclusion_or_exclusion_of($type, $attrs)
method validates_numericality_of (line 306) | public function validates_numericality_of($attrs)
method validates_size_of (line 388) | public function validates_size_of($attrs)
method validates_format_of (line 415) | public function validates_format_of($attrs)
method validates_length_of (line 462) | public function validates_length_of($attrs)
method validates_uniqueness_of (line 563) | public function validates_uniqueness_of($attrs)
method is_null_with_option (line 614) | private function is_null_with_option($var, &$options)
method is_blank_with_option (line 619) | private function is_blank_with_option($var, &$options)
class Errors (line 630) | class Errors implements IteratorAggregate
method __construct (line 664) | public function __construct(Model $model)
method clear_model (line 673) | public function clear_model()
method add (line 684) | public function add($attribute, $msg)
method add_on_empty (line 701) | public function add_on_empty($attribute, $msg)
method __get (line 716) | public function __get($attribute)
method add_on_blank (line 730) | public function add_on_blank($attribute, $msg)
method is_invalid (line 745) | public function is_invalid($attribute)
method on (line 756) | public function on($attribute)
method get_raw_errors (line 775) | public function get_raw_errors()
method full_messages (line 794) | public function full_messages()
method to_array (line 822) | public function to_array($closure=null)
method __toString (line 856) | public function __toString()
method is_empty (line 865) | public function is_empty()
method clear (line 873) | public function clear()
method size (line 882) | public function size()
method getIterator (line 907) | public function getIterator()
FILE: lib/adapters/MysqlAdapter.php
class MysqlAdapter (line 12) | class MysqlAdapter extends Connection
method limit (line 16) | public function limit($sql, $offset, $limit)
method query_column_info (line 23) | public function query_column_info($table)
method query_for_tables (line 28) | public function query_for_tables()
method create_column (line 33) | public function create_column(&$column)
method set_encoding (line 73) | public function set_encoding($charset)
method accepts_limit_and_order_for_update_and_delete (line 79) | public function accepts_limit_and_order_for_update_and_delete() { retu...
method native_database_types (line 81) | public function native_database_types()
FILE: lib/adapters/OciAdapter.php
class OciAdapter (line 14) | class OciAdapter extends Connection
method __construct (line 21) | protected function __construct($info)
method supports_sequences (line 31) | public function supports_sequences() { return true; }
method get_next_sequence_value (line 33) | public function get_next_sequence_value($sequence_name)
method next_sequence_value (line 38) | public function next_sequence_value($sequence_name)
method date_to_string (line 43) | public function date_to_string($datetime)
method datetime_to_string (line 48) | public function datetime_to_string($datetime)
method string_to_datetime (line 54) | public function string_to_datetime($string)
method limit (line 59) | public function limit($sql, $offset, $limit)
method query_column_info (line 68) | public function query_column_info($table)
method query_for_tables (line 85) | public function query_for_tables()
method create_column (line 90) | public function create_column(&$column)
method set_encoding (line 124) | public function set_encoding($charset)
method native_database_types (line 129) | public function native_database_types()
FILE: lib/adapters/PgsqlAdapter.php
class PgsqlAdapter (line 12) | class PgsqlAdapter extends Connection
method supports_sequences (line 17) | public function supports_sequences()
method get_sequence_name (line 22) | public function get_sequence_name($table, $column_name)
method next_sequence_value (line 27) | public function next_sequence_value($sequence_name)
method limit (line 32) | public function limit($sql, $offset, $limit)
method query_column_info (line 37) | public function query_column_info($table)
method query_for_tables (line 67) | public function query_for_tables()
method create_column (line 72) | public function create_column(&$column)
method set_encoding (line 116) | public function set_encoding($charset)
method native_database_types (line 121) | public function native_database_types()
FILE: lib/adapters/SqliteAdapter.php
class SqliteAdapter (line 14) | class SqliteAdapter extends Connection
method __construct (line 19) | protected function __construct($info)
method limit (line 27) | public function limit($sql, $offset, $limit)
method query_column_info (line 34) | public function query_column_info($table)
method query_for_tables (line 39) | public function query_for_tables()
method create_column (line 44) | public function create_column($column)
method set_encoding (line 87) | public function set_encoding($charset)
method accepts_limit_and_order_for_update_and_delete (line 92) | public function accepts_limit_and_order_for_update_and_delete() { retu...
method native_database_types (line 94) | public function native_database_types()
FILE: lib/cache/Memcache.php
class Memcache (line 4) | class Memcache
method __construct (line 21) | public function __construct($options)
method flush (line 36) | public function flush()
method read (line 41) | public function read($key)
method write (line 46) | public function write($key, $value, $expire)
method delete (line 51) | public function delete($key)
FILE: test/ActiveRecordCacheTest.php
class ActiveRecordCacheTest (line 4) | class ActiveRecordCacheTest extends DatabaseTest
method set_up (line 6) | public function set_up($connection_name=null)
method tear_down (line 18) | public function tear_down()
method test_default_expire (line 24) | public function test_default_expire()
method test_explicit_default_expire (line 29) | public function test_explicit_default_expire()
method test_caches_column_meta_data (line 35) | public function test_caches_column_meta_data()
FILE: test/ActiveRecordFindTest.php
class ActiveRecordFindTest (line 3) | class ActiveRecordFindTest extends DatabaseTest
method test_find_with_no_params (line 8) | public function test_find_with_no_params()
method test_find_by_pk (line 13) | public function test_find_by_pk()
method test_find_by_pkno_results (line 22) | public function test_find_by_pkno_results()
method test_find_by_multiple_pk_with_partial_match (line 27) | public function test_find_by_multiple_pk_with_partial_match()
method test_find_by_pk_with_options (line 40) | public function test_find_by_pk_with_options()
method test_find_by_pk_array (line 47) | public function test_find_by_pk_array()
method test_find_by_pk_array_with_options (line 55) | public function test_find_by_pk_array_with_options()
method test_find_nothing_with_sql_in_string (line 65) | public function test_find_nothing_with_sql_in_string()
method test_find_all (line 70) | public function test_find_all()
method test_find_all_with_no_bind_values (line 76) | public function test_find_all_with_no_bind_values()
method test_find_all_with_empty_array_bind_value_throws_exception (line 85) | public function test_find_all_with_empty_array_bind_value_throws_excep...
method test_find_hash_using_alias (line 91) | public function test_find_hash_using_alias()
method test_find_hash_using_alias_with_null (line 97) | public function test_find_hash_using_alias_with_null()
method test_dynamic_finder_using_alias (line 103) | public function test_dynamic_finder_using_alias()
method test_find_all_hash (line 108) | public function test_find_all_hash()
method test_find_all_hash_with_order (line 114) | public function test_find_all_hash_with_order()
method test_find_all_no_args (line 120) | public function test_find_all_no_args()
method test_find_all_no_results (line 126) | public function test_find_all_no_results()
method test_find_first (line 132) | public function test_find_first()
method test_find_first_no_results (line 139) | public function test_find_first_no_results()
method test_find_first_using_pk (line 144) | public function test_find_first_using_pk()
method test_find_first_with_conditions_as_string (line 150) | public function test_find_first_with_conditions_as_string()
method test_find_all_with_conditions_as_string (line 156) | public function test_find_all_with_conditions_as_string()
method test_find_by_sql (line 162) | public function test_find_by_sql()
method test_find_by_sqltakes_values_array (line 169) | public function test_find_by_sqltakes_values_array()
method test_find_with_conditions (line 175) | public function test_find_with_conditions()
method test_find_last (line 181) | public function test_find_last()
method test_find_last_using_string_condition (line 188) | public function test_find_last_using_string_condition()
method test_limit_before_order (line 195) | public function test_limit_before_order()
method test_for_each (line 202) | public function test_for_each()
method test_fetch_all (line 215) | public function test_fetch_all()
method test_count (line 227) | public function test_count()
method test_gh149_empty_count (line 237) | public function test_gh149_empty_count()
method test_exists (line 244) | public function test_exists()
method test_find_by_call_static (line 253) | public function test_find_by_call_static()
method test_find_by_call_static_no_results (line 261) | public function test_find_by_call_static_no_results()
method test_find_by_call_static_invalid_column_name (line 270) | public function test_find_by_call_static_invalid_column_name()
method test_find_all_by_call_static (line 275) | public function test_find_all_by_call_static()
method test_find_all_by_call_static_no_results (line 286) | public function test_find_all_by_call_static_no_results()
method test_find_all_by_call_static_with_array_values_and_options (line 292) | public function test_find_all_by_call_static_with_array_values_and_opt...
method test_find_all_by_call_static_undefined_method (line 302) | public function test_find_all_by_call_static_undefined_method()
method test_find_all_takes_limit_options (line 307) | public function test_find_all_takes_limit_options()
method test_find_by_call_static_with_invalid_field_name (line 316) | public function test_find_by_call_static_with_invalid_field_name()
method test_find_with_select (line 321) | public function test_find_with_select()
method test_find_with_select_non_selected_fields_should_not_have_attributes (line 328) | public function test_find_with_select_non_selected_fields_should_not_h...
method test_joins_on_model_with_association_and_explicit_joins (line 339) | public function test_joins_on_model_with_association_and_explicit_joins()
method test_joins_on_model_with_explicit_joins (line 347) | public function test_joins_on_model_with_explicit_joins()
method test_group (line 353) | public function test_group()
method test_group_with_order_and_limit_and_having (line 360) | public function test_group_with_order_and_limit_and_having()
method test_escape_quotes (line 367) | public function test_escape_quotes()
method test_from (line 373) | public function test_from()
method test_having (line 384) | public function test_having()
method test_from_with_invalid_table (line 407) | public function test_from_with_invalid_table()
method test_find_with_hash (line 412) | public function test_find_with_hash()
method test_find_or_create_by_on_existing_record (line 420) | public function test_find_or_create_by_on_existing_record()
method test_find_or_create_by_creates_new_record (line 425) | public function test_find_or_create_by_creates_new_record()
method test_find_or_create_by_throws_exception_when_using_or (line 435) | public function test_find_or_create_by_throws_exception_when_using_or()
method test_find_by_zero (line 443) | public function test_find_by_zero()
method test_find_by_null (line 451) | public function test_find_by_null()
method test_count_by (line 456) | public function test_count_by()
method test_find_by_pk_should_not_use_limit (line 463) | public function test_find_by_pk_should_not_use_limit()
method test_find_by_datetime (line 469) | public function test_find_by_datetime()
FILE: test/ActiveRecordTest.php
class ActiveRecordTest (line 3) | class ActiveRecordTest extends DatabaseTest
method set_up (line 5) | public function set_up($connection_name=null)
method test_options_is_not (line 11) | public function test_options_is_not()
method test_options_hash_with_unknown_keys (line 23) | public function test_options_hash_with_unknown_keys() {
method test_options_is_hash (line 27) | public function test_options_is_hash()
method test_extract_and_validate_options (line 32) | public function test_extract_and_validate_options() {
method test_extract_and_validate_options_with_array_in_args (line 38) | public function test_extract_and_validate_options_with_array_in_args() {
method test_extract_and_validate_options_removes_options_hash (line 43) | public function test_extract_and_validate_options_removes_options_hash...
method test_extract_and_validate_options_nope (line 49) | public function test_extract_and_validate_options_nope() {
method test_extract_and_validate_options_nope_because_wasnt_at_end (line 55) | public function test_extract_and_validate_options_nope_because_wasnt_a...
method test_invalid_attribute (line 63) | public function test_invalid_attribute()
method test_invalid_attributes (line 69) | public function test_invalid_attributes()
method test_getter_undefined_property_exception_includes_model_name (line 82) | public function test_getter_undefined_property_exception_includes_mode...
method test_mass_assignment_undefined_property_exception_includes_model_name (line 91) | public function test_mass_assignment_undefined_property_exception_incl...
method test_setter_undefined_property_exception_includes_model_name (line 99) | public function test_setter_undefined_property_exception_includes_mode...
method test_get_values_for (line 108) | public function test_get_values_for()
method test_hyphenated_column_names_to_underscore (line 116) | public function test_hyphenated_column_names_to_underscore()
method test_column_names_with_spaces (line 125) | public function test_column_names_with_spaces()
method test_mixed_case_column_name (line 134) | public function test_mixed_case_column_name()
method test_mixed_case_primary_key_save (line 140) | public function test_mixed_case_primary_key_save()
method test_reload (line 148) | public function test_reload()
method test_reload_protected_attribute (line 158) | public function test_reload_protected_attribute()
method test_active_record_model_home_not_set (line 167) | public function test_active_record_model_home_not_set()
method test_auto_load_with_namespaced_model (line 176) | public function test_auto_load_with_namespaced_model()
method test_namespace_gets_stripped_from_table_name (line 181) | public function test_namespace_gets_stripped_from_table_name()
method test_namespace_gets_stripped_from_inferred_foreign_key (line 187) | public function test_namespace_gets_stripped_from_inferred_foreign_key()
method test_namespaced_relationship_associates_correctly (line 197) | public function test_namespaced_relationship_associates_correctly()
method test_should_have_all_column_attributes_when_initializing_with_array (line 231) | public function test_should_have_all_column_attributes_when_initializi...
method test_defaults (line 237) | public function test_defaults()
method test_alias_attribute_getter (line 243) | public function test_alias_attribute_getter()
method test_alias_attribute_setter (line 250) | public function test_alias_attribute_setter()
method test_alias_from_mass_attributes (line 262) | public function test_alias_from_mass_attributes()
method test_gh18_isset_on_aliased_attribute (line 269) | public function test_gh18_isset_on_aliased_attribute()
method test_attr_accessible (line 274) | public function test_attr_accessible()
method test_attr_protected (line 283) | public function test_attr_protected()
method test_isset (line 291) | public function test_isset()
method test_readonly_only_halt_on_write_method (line 298) | public function test_readonly_only_halt_on_write_method()
method test_cast_when_using_setter (line 313) | public function test_cast_when_using_setter()
method test_cast_when_loading (line 320) | public function test_cast_when_loading()
method test_cast_defaults (line 327) | public function test_cast_defaults()
method test_transaction_committed (line 333) | public function test_transaction_committed()
method test_transaction_committed_when_returning_true (line 341) | public function test_transaction_committed_when_returning_true()
method test_transaction_rolledback_by_returning_false (line 349) | public function test_transaction_rolledback_by_returning_false()
method test_transaction_rolledback_by_throwing_exception (line 363) | public function test_transaction_rolledback_by_throwing_exception()
method test_delegate (line 385) | public function test_delegate()
method test_delegate_prefix (line 392) | public function test_delegate_prefix()
method test_delegate_returns_null_if_relationship_does_not_exist (line 398) | public function test_delegate_returns_null_if_relationship_does_not_ex...
method test_delegate_set_attribute (line 404) | public function test_delegate_set_attribute()
method test_delegate_getter_gh_98 (line 411) | public function test_delegate_getter_gh_98()
method test_delegate_setter_gh_98 (line 422) | public function test_delegate_setter_gh_98()
method test_table_name_with_underscores (line 433) | public function test_table_name_with_underscores()
method test_model_should_default_as_new_record (line 438) | public function test_model_should_default_as_new_record()
method test_setter (line 444) | public function test_setter()
method test_setter_with_same_name_as_an_attribute (line 451) | public function test_setter_with_same_name_as_an_attribute()
method test_getter (line 458) | public function test_getter()
method test_getter_with_same_name_as_an_attribute (line 464) | public function test_getter_with_same_name_as_an_attribute()
method test_setting_invalid_date_should_set_date_to_null (line 473) | public function test_setting_invalid_date_should_set_date_to_null()
method test_table_name (line 480) | public function test_table_name()
method test_undefined_instance_method (line 488) | public function test_undefined_instance_method()
method test_clear_cache_for_specific_class (line 493) | public function test_clear_cache_for_specific_class()
method test_flag_dirty (line 504) | public function test_flag_dirty()
method test_flag_dirty_attribute_which_does_not_exit (line 514) | public function test_flag_dirty_attribute_which_does_not_exit()
method test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_dirty (line 522) | public function test_gh245_dirty_attribute_should_not_raise_php_notice...
method test_assigning_php_datetime_gets_converted_to_date_class_with_defaults (line 529) | public function test_assigning_php_datetime_gets_converted_to_date_cla...
method test_assigning_php_datetime_gets_converted_to_date_class_with_custom_date_class (line 537) | public function test_assigning_php_datetime_gets_converted_to_date_cla...
method test_assigning_from_mass_assignment_php_datetime_gets_converted_to_ar_datetime (line 546) | public function test_assigning_from_mass_assignment_php_datetime_gets_...
method test_get_real_attribute_name (line 552) | public function test_get_real_attribute_name()
method test_id_setter_works_with_table_without_pk_named_attribute (line 560) | public function test_id_setter_works_with_table_without_pk_named_attri...
method test_query (line 566) | public function test_query()
FILE: test/ActiveRecordWriteTest.php
class DirtyAuthor (line 4) | class DirtyAuthor extends ActiveRecord\Model
method before_save (line 9) | public function before_save()
class AuthorWithoutSequence (line 15) | class AuthorWithoutSequence extends ActiveRecord\Model
class AuthorExplicitSequence (line 21) | class AuthorExplicitSequence extends ActiveRecord\Model
class ActiveRecordWriteTest (line 26) | class ActiveRecordWriteTest extends DatabaseTest
method make_new_book_and (line 28) | private function make_new_book_and($save=true)
method test_save (line 40) | public function test_save()
method test_insert (line 46) | public function test_insert()
method test_insert_with_no_sequence_defined (line 56) | public function test_insert_with_no_sequence_defined()
method test_insert_should_quote_keys (line 64) | public function test_insert_should_quote_keys()
method test_save_auto_increment_id (line 71) | public function test_save_auto_increment_id()
method test_sequence_was_set (line 78) | public function test_sequence_was_set()
method test_sequence_was_explicitly_set (line 86) | public function test_sequence_was_explicitly_set()
method test_delete (line 94) | public function test_delete()
method test_delete_by_find_all (line 102) | public function test_delete_by_find_all()
method test_update (line 113) | public function test_update()
method test_update_should_quote_keys (line 124) | public function test_update_should_quote_keys()
method test_update_attributes (line 132) | public function test_update_attributes()
method test_update_attributes_undefined_property (line 146) | public function test_update_attributes_undefined_property()
method test_update_attribute (line 152) | public function test_update_attribute()
method test_update_attribute_undefined_property (line 165) | public function test_update_attribute_undefined_property()
method test_save_null_value (line 171) | public function test_save_null_value()
method test_save_blank_value (line 179) | public function test_save_blank_value()
method test_dirty_attributes (line 191) | public function test_dirty_attributes()
method test_dirty_attributes_cleared_after_saving (line 197) | public function test_dirty_attributes_cleared_after_saving()
method test_dirty_attributes_cleared_after_inserting (line 205) | public function test_dirty_attributes_cleared_after_inserting()
method test_no_dirty_attributes_but_still_insert_record (line 211) | public function test_no_dirty_attributes_but_still_insert_record()
method test_dirty_attributes_cleared_after_updating (line 220) | public function test_dirty_attributes_cleared_after_updating()
method test_dirty_attributes_after_reloading (line 228) | public function test_dirty_attributes_after_reloading()
method test_dirty_attributes_with_mass_assignment (line 236) | public function test_dirty_attributes_with_mass_assignment()
method test_timestamps_set_before_save (line 243) | public function test_timestamps_set_before_save()
method test_timestamps_updated_at_only_set_before_update (line 253) | public function test_timestamps_updated_at_only_set_before_update()
method test_create (line 269) | public function test_create()
method test_create_should_set_created_at (line 275) | public function test_create_should_set_created_at()
method test_update_with_no_primary_key_defined (line 284) | public function test_update_with_no_primary_key_defined()
method test_delete_with_no_primary_key_defined (line 295) | public function test_delete_with_no_primary_key_defined()
method test_inserting_with_explicit_pk (line 302) | public function test_inserting_with_explicit_pk()
method test_readonly (line 311) | public function test_readonly()
method test_modified_attributes_in_before_handlers_get_saved (line 317) | public function test_modified_attributes_in_before_handlers_get_saved()
method test_is_dirty (line 325) | public function test_is_dirty()
method test_set_date_flags_dirty (line 334) | public function test_set_date_flags_dirty()
method test_set_date_flags_dirty_with_php_datetime (line 342) | public function test_set_date_flags_dirty_with_php_datetime()
method test_delete_all_with_conditions_as_string (line 350) | public function test_delete_all_with_conditions_as_string()
method test_delete_all_with_conditions_as_hash (line 356) | public function test_delete_all_with_conditions_as_hash()
method test_delete_all_with_conditions_as_array (line 362) | public function test_delete_all_with_conditions_as_array()
method test_delete_all_with_limit_and_order (line 368) | public function test_delete_all_with_limit_and_order()
method test_update_all_with_set_as_string (line 378) | public function test_update_all_with_set_as_string()
method test_update_all_with_set_as_hash (line 385) | public function test_update_all_with_set_as_hash()
method test_update_all_with_conditions_as_string (line 400) | public function test_update_all_with_conditions_as_string()
method test_update_all_with_conditions_as_hash (line 406) | public function test_update_all_with_conditions_as_hash()
method test_update_all_with_conditions_as_array (line 412) | public function test_update_all_with_conditions_as_array()
method test_update_all_with_limit_and_order (line 418) | public function test_update_all_with_limit_and_order()
method test_update_native_datetime (line 428) | public function test_update_native_datetime()
method test_update_our_datetime (line 436) | public function test_update_our_datetime()
FILE: test/CacheModelTest.php
class CacheModelTest (line 4) | class CacheModelTest extends DatabaseTest
method set_up (line 6) | public function set_up($connection_name=null)
method set_method_public (line 17) | protected static function set_method_public($className, $methodName)
method tear_down (line 25) | public function tear_down()
method test_default_expire (line 31) | public function test_default_expire()
method test_explicit_expire (line 36) | public function test_explicit_expire()
method test_cache_key (line 41) | public function test_cache_key()
method test_model_cache_find_by_pk (line 49) | public function test_model_cache_find_by_pk()
method test_model_cache_new (line 59) | public function test_model_cache_new()
method test_model_cache_find (line 76) | public function test_model_cache_find()
method test_regular_models_not_cached (line 90) | public function test_regular_models_not_cached()
method test_model_delete_from_cache (line 98) | public function test_model_delete_from_cache()
method test_model_update_cache (line 111) | public function test_model_update_cache(){
method test_model_reload_expires_cache (line 131) | public function test_model_reload_expires_cache(){
FILE: test/CacheTest.php
class CacheTest (line 4) | class CacheTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 6) | public function set_up()
method tear_down (line 17) | public function tear_down()
method cache_get (line 22) | private function cache_get()
method test_initialize (line 27) | public function test_initialize()
method test_initialize_with_null (line 32) | public function test_initialize_with_null()
method test_get_returns_the_value (line 38) | public function test_get_returns_the_value()
method test_get_writes_to_the_cache (line 43) | public function test_get_writes_to_the_cache()
method test_get_does_not_execute_closure_on_cache_hit (line 49) | public function test_get_does_not_execute_closure_on_cache_hit()
method test_cache_adapter_returns_false_on_cache_miss (line 55) | public function test_cache_adapter_returns_false_on_cache_miss()
method test_get_works_without_caching_enabled (line 60) | public function test_get_works_without_caching_enabled()
method test_cache_expire (line 66) | public function test_cache_expire()
method test_namespace_is_set_properly (line 75) | public function test_namespace_is_set_properly()
method test_exception_when_connect_fails (line 86) | public function test_exception_when_connect_fails()
FILE: test/CallbackTest.php
class CallBackTest (line 3) | class CallBackTest extends DatabaseTest
method set_up (line 5) | public function set_up($connection_name=null)
method assert_has_callback (line 15) | public function assert_has_callback($callback_name, $method_name=null)
method assert_implicit_save (line 23) | public function assert_implicit_save($first_method, $second_method)
method test_gh_266_calling_save_in_after_save_callback_uses_update_instead_of_insert (line 32) | public function test_gh_266_calling_save_in_after_save_callback_uses_u...
method test_generic_callback_was_auto_registered (line 45) | public function test_generic_callback_was_auto_registered()
method test_register (line 50) | public function test_register()
method test_register_non_generic (line 56) | public function test_register_non_generic()
method test_register_invalid_callback (line 65) | public function test_register_invalid_callback()
method test_register_callback_with_undefined_method (line 73) | public function test_register_callback_with_undefined_method()
method test_register_with_string_definition (line 78) | public function test_register_with_string_definition()
method test_register_with_closure (line 84) | public function test_register_with_closure()
method test_register_with_null_definition (line 89) | public function test_register_with_null_definition()
method test_register_with_no_definition (line 95) | public function test_register_with_no_definition()
method test_register_appends_to_registry (line 101) | public function test_register_appends_to_registry()
method test_register_prepends_to_registry (line 108) | public function test_register_prepends_to_registry()
method test_registers_via_static_array_definition (line 115) | public function test_registers_via_static_array_definition()
method test_registers_via_static_string_definition (line 121) | public function test_registers_via_static_string_definition()
method test_register_via_static_with_invalid_definition (line 129) | public function test_register_via_static_with_invalid_definition()
method test_can_register_same_multiple_times (line 137) | public function test_can_register_same_multiple_times()
method test_register_closure_callback (line 144) | public function test_register_closure_callback()
method test_get_callbacks_returns_array (line 151) | public function test_get_callbacks_returns_array()
method test_get_callbacks_returns_null (line 157) | public function test_get_callbacks_returns_null()
method test_invoke_runs_all_callbacks (line 162) | public function test_invoke_runs_all_callbacks()
method test_invoke_closure (line 170) | public function test_invoke_closure()
method test_invoke_implicitly_calls_save_first (line 178) | public function test_invoke_implicitly_calls_save_first()
method test_invoke_unregistered_callback (line 189) | public function test_invoke_unregistered_callback()
method test_before_callbacks_pass_on_false_return_callback_returned_false (line 195) | public function test_before_callbacks_pass_on_false_return_callback_re...
method test_before_callbacks_does_not_pass_on_false_for_after_callbacks (line 201) | public function test_before_callbacks_does_not_pass_on_false_for_after...
method test_gh_28_after_create_should_be_invoked_after_auto_incrementing_pk_is_set (line 207) | public function test_gh_28_after_create_should_be_invoked_after_auto_i...
method test_before_create_returned_false_halts_execution (line 219) | public function test_before_create_returned_false_halts_execution()
method test_before_save_returned_false_halts_execution (line 240) | public function test_before_save_returned_false_halts_execution()
method test_before_destroy_returned_false_halts_execution (line 262) | public function test_before_destroy_returned_false_halts_execution()
method test_before_validation_returned_false_halts_execution (line 280) | public function test_before_validation_returned_false_halts_execution()
FILE: test/ColumnTest.php
class ColumnTest (line 7) | class ColumnTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 9) | public function set_up()
method assert_mapped_type (line 19) | public function assert_mapped_type($type, $raw_type)
method assert_cast (line 25) | public function assert_cast($type, $casted_value, $original_value)
method test_map_raw_type_dates (line 36) | public function test_map_raw_type_dates()
method test_map_raw_type_integers (line 42) | public function test_map_raw_type_integers()
method test_map_raw_type_decimals (line 52) | public function test_map_raw_type_decimals()
method test_map_raw_type_strings (line 60) | public function test_map_raw_type_strings()
method test_map_raw_type_default_to_string (line 67) | public function test_map_raw_type_default_to_string()
method test_map_raw_type_changes_integer_to_int (line 72) | public function test_map_raw_type_changes_integer_to_int()
method test_cast (line 79) | public function test_cast()
method test_cast_leave_null_alone (line 100) | public function test_cast_leave_null_alone()
method test_empty_and_null_date_strings_should_return_null (line 114) | public function test_empty_and_null_date_strings_should_return_null()
method test_empty_and_null_datetime_strings_should_return_null (line 122) | public function test_empty_and_null_datetime_strings_should_return_null()
method test_native_date_time_attribute_copies_exact_tz (line 130) | public function test_native_date_time_attribute_copies_exact_tz()
method test_ar_date_time_attribute_copies_exact_tz (line 144) | public function test_ar_date_time_attribute_copies_exact_tz()
FILE: test/ConfigTest.php
class TestLogger (line 6) | class TestLogger
method log (line 8) | private function log() {}
class TestDateTimeWithoutCreateFromFormat (line 11) | class TestDateTimeWithoutCreateFromFormat
method format (line 13) | public function format($format=null) {}
class TestDateTime (line 16) | class TestDateTime
method format (line 18) | public function format($format=null) {}
method createFromFormat (line 19) | public static function createFromFormat($format, $time) {}
class ConfigTest (line 22) | class ConfigTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 24) | public function set_up()
method test_set_connections_must_be_array (line 34) | public function test_set_connections_must_be_array()
method test_get_connections (line 39) | public function test_get_connections()
method test_get_connection (line 44) | public function test_get_connection()
method test_get_invalid_connection (line 49) | public function test_get_invalid_connection()
method test_get_default_connection_and_connection (line 54) | public function test_get_default_connection_and_connection()
method test_get_default_connection_and_connection_string_defaults_to_development (line 61) | public function test_get_default_connection_and_connection_string_defa...
method test_get_default_connection_string_when_connection_name_is_not_valid (line 67) | public function test_get_default_connection_string_when_connection_nam...
method test_default_connection_is_set_when_only_one_connection_is_present (line 73) | public function test_default_connection_is_set_when_only_one_connectio...
method test_set_connections_with_default (line 79) | public function test_set_connections_with_default()
method test_get_date_class_with_default (line 85) | public function test_get_date_class_with_default()
method test_set_date_class_when_class_doesnt_exist (line 93) | public function test_set_date_class_when_class_doesnt_exist()
method test_set_date_class_when_class_doesnt_have_format_or_createfromformat (line 101) | public function test_set_date_class_when_class_doesnt_have_format_or_c...
method test_set_date_class_when_class_doesnt_have_createfromformat (line 109) | public function test_set_date_class_when_class_doesnt_have_createfromf...
method test_set_date_class_with_valid_class (line 114) | public function test_set_date_class_with_valid_class()
method test_initialize_closure (line 120) | public function test_initialize_closure()
method test_logger_object_must_implement_log_method (line 131) | public function test_logger_object_must_implement_log_method()
FILE: test/ConnectionManagerTest.php
class ConnectionManagerTest (line 6) | class ConnectionManagerTest extends DatabaseTest
method test_get_connection_with_null_connection (line 8) | public function test_get_connection_with_null_connection()
method test_get_connection (line 14) | public function test_get_connection()
method test_get_connection_uses_existing_object (line 19) | public function test_get_connection_uses_existing_object()
method test_get_connection_with_default (line 25) | public function test_get_connection_with_default()
method test_gh_91_get_connection_with_null_connection_is_always_default (line 32) | public function test_gh_91_get_connection_with_null_connection_is_alwa...
method test_drop_connection (line 44) | public function test_drop_connection()
method test_drop_connection_with_default (line 51) | public function test_drop_connection_with_default()
FILE: test/ConnectionTest.php
class ConnectionTest (line 8) | class ConnectionTest extends SnakeCase_PHPUnit_Framework_TestCase
method test_connection_info_from_should_throw_exception_when_no_host (line 13) | public function test_connection_info_from_should_throw_exception_when_...
method test_connection_info (line 18) | public function test_connection_info()
method test_gh_103_sqlite_connection_string_relative (line 29) | public function test_gh_103_sqlite_connection_string_relative()
method test_gh_103_sqlite_connection_string_absolute (line 38) | public function test_gh_103_sqlite_connection_string_absolute()
method test_gh_103_sqlite_connection_string_unix (line 43) | public function test_gh_103_sqlite_connection_string_unix()
method test_gh_103_sqlite_connection_string_windows (line 55) | public function test_gh_103_sqlite_connection_string_windows()
method test_parse_connection_url_with_unix_sockets (line 61) | public function test_parse_connection_url_with_unix_sockets()
method test_parse_connection_url_with_decode_option (line 67) | public function test_parse_connection_url_with_decode_option()
method test_encoding (line 74) | public function test_encoding()
FILE: test/DateFormatTest.php
class DateFormatTest (line 3) | class DateFormatTest extends DatabaseTest
method test_datefield_gets_converted_to_ar_datetime (line 6) | public function test_datefield_gets_converted_to_ar_datetime()
FILE: test/DateTimeTest.php
class DateTimeTest (line 5) | class DateTimeTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 7) | public function set_up()
method tear_down (line 13) | public function tear_down()
method get_model (line 18) | private function get_model()
method assert_dirtifies (line 29) | private function assert_dirtifies($method /*, method params, ...*/)
method test_should_flag_the_attribute_dirty (line 42) | public function test_should_flag_the_attribute_dirty()
method test_set_iso_date (line 56) | public function test_set_iso_date()
method test_set_time (line 67) | public function test_set_time()
method test_set_time_microseconds (line 78) | public function test_set_time_microseconds()
method test_get_format_with_friendly (line 89) | public function test_get_format_with_friendly()
method test_get_format_with_format (line 94) | public function test_get_format_with_format()
method test_get_format_with_null (line 99) | public function test_get_format_with_null()
method test_format (line 104) | public function test_format()
method test_format_by_friendly_name (line 110) | public function test_format_by_friendly_name()
method test_format_by_custom_format (line 116) | public function test_format_by_custom_format()
method test_format_uses_default (line 122) | public function test_format_uses_default()
method test_all_formats (line 128) | public function test_all_formats()
method test_change_default_format_to_format_string (line 134) | public function test_change_default_format_to_format_string()
method test_change_default_format_to_friently (line 140) | public function test_change_default_format_to_friently()
method test_to_string (line 146) | public function test_to_string()
method test_create_from_format_error_handling (line 151) | public function test_create_from_format_error_handling()
method test_create_from_format_without_tz (line 157) | public function test_create_from_format_without_tz()
method test_create_from_format_with_tz (line 163) | public function test_create_from_format_with_tz()
method test_native_date_time_attribute_copies_exact_tz (line 171) | public function test_native_date_time_attribute_copies_exact_tz()
method test_ar_date_time_attribute_copies_exact_tz (line 185) | public function test_ar_date_time_attribute_copies_exact_tz()
method test_clone (line 199) | public function test_clone()
FILE: test/ExpressionsTest.php
class ExpressionsTest (line 8) | class ExpressionsTest extends SnakeCase_PHPUnit_Framework_TestCase
method test_values (line 10) | public function test_values()
method test_one_variable (line 16) | public function test_one_variable()
method test_array_variable (line 23) | public function test_array_variable()
method test_multiple_variables (line 29) | public function test_multiple_variables()
method test_to_string (line 36) | public function test_to_string()
method test_to_string_with_array_variable (line 42) | public function test_to_string_with_array_variable()
method test_to_string_with_null_options (line 48) | public function test_to_string_with_null_options()
method test_insufficient_variables (line 58) | public function test_insufficient_variables()
method test_no_values (line 64) | public function test_no_values()
method test_null_variable (line 71) | public function test_null_variable()
method test_zero_variable (line 78) | public function test_zero_variable()
method test_empty_array_variable (line 85) | public function test_empty_array_variable()
method test_ignore_invalid_parameter_marker (line 92) | public function test_ignore_invalid_parameter_marker()
method test_ignore_parameter_marker_with_escaped_quote (line 98) | public function test_ignore_parameter_marker_with_escaped_quote()
method test_ignore_parameter_marker_with_backspace_escaped_quote (line 104) | public function test_ignore_parameter_marker_with_backspace_escaped_qu...
method test_substitute (line 110) | public function test_substitute()
method test_substitute_quotes_scalars_but_not_others (line 116) | public function test_substitute_quotes_scalars_but_not_others()
method test_substitute_where_value_has_question_mark (line 122) | public function test_substitute_where_value_has_question_mark()
method test_substitute_array_value (line 128) | public function test_substitute_array_value()
method test_substitute_escapes_quotes (line 134) | public function test_substitute_escapes_quotes()
method test_substitute_escape_quotes_with_connections_escape_method (line 140) | public function test_substitute_escape_quotes_with_connections_escape_...
method test_bind (line 153) | public function test_bind()
method test_bind_overwrite_existing (line 160) | public function test_bind_overwrite_existing()
method test_bind_invalid_parameter_number (line 170) | public function test_bind_invalid_parameter_number()
method test_subsitute_using_alternate_values (line 176) | public function test_subsitute_using_alternate_values()
method test_null_value (line 184) | public function test_null_value()
method test_hash_with_default_glue (line 190) | public function test_hash_with_default_glue()
method test_hash_with_glue (line 196) | public function test_hash_with_glue()
method test_hash_with_array (line 202) | public function test_hash_with_array()
FILE: test/HasManyThroughTest.php
class HasManyThroughTest (line 7) | class HasManyThroughTest extends DatabaseTest
method test_gh101_has_many_through (line 9) | public function test_gh101_has_many_through()
method test_gh101_has_many_through_include (line 26) | public function test_gh101_has_many_through_include()
method test_gh107_has_many_through_include_eager (line 38) | public function test_gh107_has_many_through_include_eager()
method test_gh107_has_many_though_include_eager_with_namespace (line 47) | public function test_gh107_has_many_though_include_eager_with_namespace()
FILE: test/InflectorTest.php
class InflectorTest (line 4) | class InflectorTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 6) | public function set_up()
method test_underscorify (line 11) | public function test_underscorify()
method test_tableize (line 17) | public function test_tableize()
method test_keyify (line 23) | public function test_keyify()
FILE: test/ModelCallbackTest.php
class ModelCallbackTest (line 3) | class ModelCallbackTest extends DatabaseTest
method set_up (line 5) | public function set_up($connection_name=null)
method register_and_invoke_callbacks (line 13) | public function register_and_invoke_callbacks($callbacks, $return, $cl...
method assert_fires (line 27) | public function assert_fires($callbacks, $closure)
method assert_does_not_fire (line 33) | public function assert_does_not_fire($callbacks, $closure)
method assert_fires_returns_false (line 39) | public function assert_fires_returns_false($callbacks, $only_fire, $cl...
method test_after_construct_fires_by_default (line 51) | public function test_after_construct_fires_by_default()
method test_fire_validation_callbacks_on_insert (line 56) | public function test_fire_validation_callbacks_on_insert()
method test_fire_validation_callbacks_on_update (line 62) | public function test_fire_validation_callbacks_on_update()
method test_validation_call_backs_not_fired_due_to_bypassing_validations (line 68) | public function test_validation_call_backs_not_fired_due_to_bypassing_...
method test_before_validation_returning_false_cancels_callbacks (line 73) | public function test_before_validation_returning_false_cancels_callbac...
method test_fires_before_save_and_before_update_when_updating (line 79) | public function test_fires_before_save_and_before_update_when_updating()
method test_before_save_returning_false_cancels_callbacks (line 85) | public function test_before_save_returning_false_cancels_callbacks()
method test_destroy (line 91) | public function test_destroy()
FILE: test/MysqlAdapterTest.php
class MysqlAdapterTest (line 6) | class MysqlAdapterTest extends AdapterTest
method set_up (line 8) | public function set_up($connection_name=null)
method test_enum (line 13) | public function test_enum()
method test_set_charset (line 21) | public function test_set_charset()
method test_limit_with_null_offset_does_not_contain_offset (line 28) | public function test_limit_with_null_offset_does_not_contain_offset()
FILE: test/OciAdapterTest.php
class OciAdapterTest (line 4) | class OciAdapterTest extends AdapterTest
method set_up (line 6) | public function set_up($connection_name=null)
method test_get_sequence_name (line 11) | public function test_get_sequence_name()
method test_columns_text (line 16) | public function test_columns_text()
method test_datetime_to_string (line 23) | public function test_datetime_to_string()
method test_date_to_string (line 28) | public function test_date_to_string()
method test_insert_id (line 33) | public function test_insert_id() {}
method test_insert_id_with_params (line 34) | public function test_insert_id_with_params() {}
method test_insert_id_should_return_explicitly_inserted_id (line 35) | public function test_insert_id_should_return_explicitly_inserted_id() {}
method test_columns_time (line 36) | public function test_columns_time() {}
method test_columns_sequence (line 37) | public function test_columns_sequence() {}
method test_set_charset (line 39) | public function test_set_charset()
FILE: test/PgsqlAdapterTest.php
class PgsqlAdapterTest (line 6) | class PgsqlAdapterTest extends AdapterTest
method set_up (line 8) | public function set_up($connection_name=null)
method test_insert_id (line 13) | public function test_insert_id()
method test_insert_id_with_params (line 19) | public function test_insert_id_with_params()
method test_insert_id_should_return_explicitly_inserted_id (line 26) | public function test_insert_id_should_return_explicitly_inserted_id()
method test_set_charset (line 32) | public function test_set_charset()
method test_gh96_columns_not_duplicated_by_index (line 39) | public function test_gh96_columns_not_duplicated_by_index()
FILE: test/RelationshipTest.php
class NotModel (line 3) | class NotModel {}
class AuthorWithNonModelRelationship (line 5) | class AuthorWithNonModelRelationship extends ActiveRecord\Model
class RelationshipTest (line 12) | class RelationshipTest extends DatabaseTest
method set_up (line 17) | public function set_up($connection_name=null)
method get_relationship (line 34) | protected function get_relationship($type=null)
method assert_default_belongs_to (line 57) | protected function assert_default_belongs_to($event, $association_name...
method assert_default_has_many (line 65) | protected function assert_default_has_many($venue, $association_name='...
method assert_default_has_one (line 72) | protected function assert_default_has_one($employee, $association_name...
method test_has_many_basic (line 79) | public function test_has_many_basic()
method test_eager_load_with_empty_nested_includes (line 84) | public function test_eager_load_with_empty_nested_includes()
method test_gh_256_eager_loading_three_levels_deep (line 92) | public function test_gh_256_eager_loading_three_levels_deep()
method test_joins_on_model_via_undeclared_association (line 114) | public function test_joins_on_model_via_undeclared_association()
method test_joins_only_loads_given_model_attributes (line 119) | public function test_joins_only_loads_given_model_attributes()
method test_joins_combined_with_select_loads_all_attributes (line 126) | public function test_joins_combined_with_select_loads_all_attributes()
method test_belongs_to_basic (line 133) | public function test_belongs_to_basic()
method test_belongs_to_returns_null_when_no_record (line 138) | public function test_belongs_to_returns_null_when_no_record()
method test_belongs_to_returns_null_when_foreign_key_is_null (line 144) | public function test_belongs_to_returns_null_when_foreign_key_is_null()
method test_belongs_to_with_explicit_class_name (line 150) | public function test_belongs_to_with_explicit_class_name()
method test_belongs_to_with_explicit_foreign_key (line 156) | public function test_belongs_to_with_explicit_foreign_key()
method test_belongs_to_with_select (line 168) | public function test_belongs_to_with_select()
method test_belongs_to_with_readonly (line 182) | public function test_belongs_to_with_readonly()
method test_belongs_to_with_plural_attribute_name (line 198) | public function test_belongs_to_with_plural_attribute_name()
method test_belongs_to_with_conditions_and_non_qualifying_record (line 204) | public function test_belongs_to_with_conditions_and_non_qualifying_rec...
method test_belongs_to_with_conditions_and_qualifying_record (line 212) | public function test_belongs_to_with_conditions_and_qualifying_record()
method test_belongs_to_build_association (line 218) | public function test_belongs_to_build_association()
method test_has_many_build_association (line 226) | public function test_has_many_build_association()
method test_belongs_to_create_association (line 233) | public function test_belongs_to_create_association()
method test_build_association_overwrites_guarded_foreign_keys (line 241) | public function test_build_association_overwrites_guarded_foreign_keys()
method test_belongs_to_can_be_self_referential (line 251) | public function test_belongs_to_can_be_self_referential()
method test_belongs_to_with_an_invalid_option (line 259) | public function test_belongs_to_with_an_invalid_option()
method test_has_many_with_explicit_class_name (line 266) | public function test_has_many_with_explicit_class_name()
method test_has_many_with_select (line 272) | public function test_has_many_with_select()
method test_has_many_with_readonly (line 286) | public function test_has_many_with_readonly()
method test_has_many_with_singular_attribute_name (line 302) | public function test_has_many_with_singular_attribute_name()
method test_has_many_with_conditions_and_non_qualifying_record (line 308) | public function test_has_many_with_conditions_and_non_qualifying_record()
method test_has_many_with_conditions_and_qualifying_record (line 316) | public function test_has_many_with_conditions_and_qualifying_record()
method test_has_many_with_sql_clause_options (line 324) | public function test_has_many_with_sql_clause_options()
method test_has_many_through (line 335) | public function test_has_many_through()
method test_gh27_has_many_through_with_explicit_keys (line 342) | public function test_gh27_has_many_through_with_explicit_keys()
method test_gh16_has_many_through_inside_a_loop_should_not_cause_an_exception (line 350) | public function test_gh16_has_many_through_inside_a_loop_should_not_ca...
method test_has_many_through_no_association (line 363) | public function test_has_many_through_no_association()
method test_has_many_through_with_select (line 373) | public function test_has_many_through_with_select()
method test_has_many_through_with_conditions (line 383) | public function test_has_many_through_with_conditions()
method test_has_many_through_using_source (line 393) | public function test_has_many_through_using_source()
method test_has_many_through_with_invalid_class_name (line 405) | public function test_has_many_through_with_invalid_class_name()
method test_has_many_with_joins (line 414) | public function test_has_many_with_joins()
method test_has_many_with_explicit_keys (line 420) | public function test_has_many_with_explicit_keys()
method test_has_one_basic (line 433) | public function test_has_one_basic()
method test_has_one_with_explicit_class_name (line 438) | public function test_has_one_with_explicit_class_name()
method test_has_one_with_select (line 444) | public function test_has_one_with_select()
method test_has_one_with_order (line 458) | public function test_has_one_with_order()
method test_has_one_with_conditions_and_non_qualifying_record (line 466) | public function test_has_one_with_conditions_and_non_qualifying_record()
method test_has_one_with_conditions_and_qualifying_record (line 474) | public function test_has_one_with_conditions_and_qualifying_record()
method test_has_one_with_readonly (line 480) | public function test_has_one_with_readonly()
method test_has_one_can_be_self_referential (line 496) | public function test_has_one_can_be_self_referential()
method test_has_one_with_joins (line 504) | public function test_has_one_with_joins()
method test_has_one_with_explicit_keys (line 510) | public function test_has_one_with_explicit_keys()
method test_dont_attempt_to_load_if_all_foreign_keys_are_null (line 519) | public function test_dont_attempt_to_load_if_all_foreign_keys_are_null()
method test_relationship_on_table_with_underscores (line 526) | public function test_relationship_on_table_with_underscores()
method test_has_one_through (line 531) | public function test_has_one_through()
method test_throw_error_if_relationship_is_not_a_model (line 541) | public function test_throw_error_if_relationship_is_not_a_model()
method test_gh93_and_gh100_eager_loading_respects_association_options (line 546) | public function test_gh93_and_gh100_eager_loading_respects_association...
method test_eager_loading_has_many_x (line 555) | public function test_eager_loading_has_many_x()
method test_eager_loading_has_many_x_with_caching (line 566) | public function test_eager_loading_has_many_x_with_caching()
method test_eager_loading_has_many_with_no_related_rows (line 573) | public function test_eager_loading_has_many_with_no_related_rows()
method test_eager_loading_has_many_array_of_includes (line 584) | public function test_eager_loading_has_many_array_of_includes()
method test_eager_loading_has_many_nested (line 610) | public function test_eager_loading_has_many_nested()
method test_eager_loading_belongs_to (line 632) | public function test_eager_loading_belongs_to()
method test_eager_loading_belongs_to_array_of_includes (line 642) | public function test_eager_loading_belongs_to_array_of_includes()
method test_eager_loading_belongs_to_nested (line 657) | public function test_eager_loading_belongs_to_nested()
method test_eager_loading_belongs_to_with_no_related_rows (line 676) | public function test_eager_loading_belongs_to_with_no_related_rows()
method test_eager_loading_clones_related_objects (line 690) | public function test_eager_loading_clones_related_objects()
method test_eager_loading_clones_nested_related_objects (line 702) | public function test_eager_loading_clones_nested_related_objects()
method test_gh_23_relationships_with_joins_to_same_table_should_alias_table_name (line 715) | public function test_gh_23_relationships_with_joins_to_same_table_shou...
method test_gh_40_relationships_with_joins_aliases_table_name_in_conditions (line 736) | public function test_gh_40_relationships_with_joins_aliases_table_name...
method test_dont_attempt_eager_load_when_record_does_not_exist (line 746) | public function test_dont_attempt_eager_load_when_record_does_not_exist()
FILE: test/SQLBuilderTest.php
class SQLBuilderTest (line 6) | class SQLBuilderTest extends DatabaseTest
method set_up (line 12) | public function set_up($connection_name=null)
method cond_from_s (line 19) | protected function cond_from_s($name, $values=null, $map=null)
method assert_conditions (line 24) | public function assert_conditions($expected_sql, $values, $underscored...
method test_no_connection (line 38) | public function test_no_connection()
method test_nothing (line 43) | public function test_nothing()
method test_where_with_array (line 48) | public function test_where_with_array()
method test_where_with_hash (line 55) | public function test_where_with_hash()
method test_where_with_hash_and_array (line 62) | public function test_where_with_hash_and_array()
method test_gh134_where_with_hash_and_null (line 69) | public function test_gh134_where_with_hash_and_null()
method test_where_with_null (line 76) | public function test_where_with_null()
method test_where_with_no_args (line 82) | public function test_where_with_no_args()
method test_order (line 88) | public function test_order()
method test_limit (line 94) | public function test_limit()
method test_select (line 100) | public function test_select()
method test_joins (line 106) | public function test_joins()
method test_group (line 113) | public function test_group()
method test_having (line 119) | public function test_having()
method test_all_clauses_after_where_should_be_correctly_ordered (line 125) | public function test_all_clauses_after_where_should_be_correctly_order...
method test_insert_requires_hash (line 138) | public function test_insert_requires_hash()
method test_insert (line 143) | public function test_insert()
method test_insert_with_null (line 149) | public function test_insert_with_null()
method test_update_with_hash (line 155) | public function test_update_with_hash()
method test_update_with_limit_and_order (line 162) | public function test_update_with_limit_and_order()
method test_update_with_string (line 171) | public function test_update_with_string()
method test_update_with_null (line 177) | public function test_update_with_null()
method test_delete (line 183) | public function test_delete()
method test_delete_with_where (line 189) | public function test_delete_with_where()
method test_delete_with_hash (line 196) | public function test_delete_with_hash()
method test_delete_with_limit_and_order (line 203) | public function test_delete_with_limit_and_order()
method test_reverse_order (line 212) | public function test_reverse_order()
method test_create_conditions_from_underscored_string (line 223) | public function test_create_conditions_from_underscored_string()
method test_create_conditions_from_underscored_string_with_nulls (line 230) | public function test_create_conditions_from_underscored_string_with_nu...
method test_create_conditions_from_underscored_string_with_missing_args (line 235) | public function test_create_conditions_from_underscored_string_with_mi...
method test_create_conditions_from_underscored_string_with_blank (line 241) | public function test_create_conditions_from_underscored_string_with_bl...
method test_create_conditions_from_underscored_string_invalid (line 246) | public function test_create_conditions_from_underscored_string_invalid()
method test_create_conditions_from_underscored_string_with_mapped_columns (line 252) | public function test_create_conditions_from_underscored_string_with_ma...
method test_create_hash_from_underscored_string (line 257) | public function test_create_hash_from_underscored_string()
method test_create_hash_from_underscored_string_with_mapped_columns (line 264) | public function test_create_hash_from_underscored_string_with_mapped_c...
method test_where_with_joins_prepends_table_name_to_fields (line 272) | public function test_where_with_joins_prepends_table_name_to_fields()
FILE: test/SerializationTest.php
class SerializationTest (line 6) | class SerializationTest extends DatabaseTest
method tear_down (line 8) | public function tear_down()
method _a (line 15) | public function _a($options=array(), $model=null)
method test_only (line 24) | public function test_only()
method test_only_not_array (line 29) | public function test_only_not_array()
method test_only_should_only_apply_to_attributes (line 34) | public function test_only_should_only_apply_to_attributes()
method test_only_overrides_except (line 40) | public function test_only_overrides_except()
method test_except (line 45) | public function test_except()
method test_except_takes_a_string (line 50) | public function test_except_takes_a_string()
method test_methods (line 55) | public function test_methods()
method test_methods_takes_a_string (line 61) | public function test_methods_takes_a_string()
method test_methods_method_same_as_attribute (line 69) | public function test_methods_method_same_as_attribute()
method test_include (line 75) | public function test_include()
method test_include_nested_with_nested_options (line 81) | public function test_include_nested_with_nested_options()
method test_datetime_values_get_converted_to_strings (line 92) | public function test_datetime_values_get_converted_to_strings()
method test_to_json (line 99) | public function test_to_json()
method test_to_json_include_root (line 106) | public function test_to_json_include_root()
method test_to_xml_include (line 112) | public function test_to_xml_include()
method test_to_xml (line 120) | public function test_to_xml()
method test_to_array (line 126) | public function test_to_array()
method test_to_array_include_root (line 133) | public function test_to_array_include_root()
method test_to_array_except (line 142) | public function test_to_array_except()
method test_works_with_datetime (line 151) | public function test_works_with_datetime()
method test_to_xml_skip_instruct (line 158) | public function test_to_xml_skip_instruct()
method test_only_method (line 164) | public function test_only_method()
method test_to_csv (line 169) | public function test_to_csv()
method test_to_csv_only_header (line 175) | public function test_to_csv_only_header()
method test_to_csv_only_method (line 183) | public function test_to_csv_only_method()
method test_to_csv_only_method_on_header (line 191) | public function test_to_csv_only_method_on_header()
method test_to_csv_with_custom_delimiter (line 200) | public function test_to_csv_with_custom_delimiter()
method test_to_csv_with_custom_enclosure (line 207) | public function test_to_csv_with_custom_enclosure()
FILE: test/SqliteAdapterTest.php
class SqliteAdapterTest (line 4) | class SqliteAdapterTest extends AdapterTest
method set_up (line 6) | public function set_up($connection_name=null)
method tearDown (line 11) | public function tearDown()
method tearDownAfterClass (line 19) | public static function tearDownAfterClass()
method testConnectToInvalidDatabaseShouldNotCreateDbFile (line 26) | public function testConnectToInvalidDatabaseShouldNotCreateDbFile()
method test_limit_with_null_offset_does_not_contain_offset (line 39) | public function test_limit_with_null_offset_does_not_contain_offset()
method test_gh183_sqliteadapter_autoincrement (line 48) | public function test_gh183_sqliteadapter_autoincrement()
method test_datetime_to_string (line 67) | public function test_datetime_to_string()
method test_date_to_string (line 73) | public function test_date_to_string()
method test_connect_with_port (line 80) | public function test_connect_with_port() {}
FILE: test/UtilsTest.php
class UtilsTest (line 5) | class UtilsTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 7) | public function set_up()
method test_collect_with_array_of_objects_using_closure (line 22) | public function test_collect_with_array_of_objects_using_closure()
method test_collect_with_array_of_objects_using_string (line 27) | public function test_collect_with_array_of_objects_using_string()
method test_collect_with_array_hash_using_closure (line 32) | public function test_collect_with_array_hash_using_closure()
method test_collect_with_array_hash_using_string (line 37) | public function test_collect_with_array_hash_using_string()
method test_array_flatten (line 42) | public function test_array_flatten()
method test_all (line 55) | public function test_all()
method test_classify (line 63) | public function test_classify()
method test_classify_singularize (line 75) | public function test_classify_singularize()
method test_singularize (line 87) | public function test_singularize()
method test_wrap_strings_in_arrays (line 98) | public function test_wrap_strings_in_arrays()
FILE: test/ValidatesFormatOfTest.php
class BookFormat (line 3) | class BookFormat extends ActiveRecord\Model
class ValidatesFormatOfTest (line 11) | class ValidatesFormatOfTest extends DatabaseTest
method set_up (line 13) | public function set_up($connection_name=null)
method test_format (line 19) | public function test_format()
method test_invalid_null (line 32) | public function test_invalid_null()
method test_invalid_blank (line 41) | public function test_invalid_blank()
method test_valid_blank_andallow_blank (line 50) | public function test_valid_blank_andallow_blank()
method test_valid_null_and_allow_null (line 59) | public function test_valid_null_and_allow_null()
method test_invalid_lack_of_with_key (line 73) | public function test_invalid_lack_of_with_key()
method test_invalid_with_expression_as_non_string (line 83) | public function test_invalid_with_expression_as_non_string()
method test_invalid_with_expression_as_non_regexp (line 91) | public function test_invalid_with_expression_as_non_regexp()
method test_custom_message (line 100) | public function test_custom_message()
FILE: test/ValidatesInclusionAndExclusionOfTest.php
class BookExclusion (line 3) | class BookExclusion extends ActiveRecord\Model
class BookInclusion (line 11) | class BookInclusion extends ActiveRecord\Model
class ValidatesInclusionAndExclusionOfTest (line 19) | class ValidatesInclusionAndExclusionOfTest extends DatabaseTest
method set_up (line 21) | public function set_up($connection_name=null)
method test_inclusion (line 28) | public function test_inclusion()
method test_exclusion (line 36) | public function test_exclusion()
method test_invalid_inclusion (line 44) | public function test_invalid_inclusion()
method test_invalid_exclusion (line 55) | public function test_invalid_exclusion()
method test_inclusion_with_numeric (line 68) | public function test_inclusion_with_numeric()
method test_inclusion_with_boolean (line 77) | public function test_inclusion_with_boolean()
method test_inclusion_with_null (line 86) | public function test_inclusion_with_null()
method test_invalid_inclusion_with_numeric (line 95) | public function test_invalid_inclusion_with_numeric()
method tes_inclusion_within_option (line 104) | public function tes_inclusion_within_option()
method tes_inclusion_scalar_value (line 113) | public function tes_inclusion_scalar_value()
method test_valid_null (line 122) | public function test_valid_null()
method test_valid_blank (line 131) | public function test_valid_blank()
method test_custom_message (line 140) | public function test_custom_message()
FILE: test/ValidatesLengthOfTest.php
class BookLength (line 3) | class BookLength extends ActiveRecord\Model
class BookSize (line 9) | class BookSize extends ActiveRecord\Model
class ValidatesLengthOfTest (line 15) | class ValidatesLengthOfTest extends DatabaseTest
method set_up (line 17) | public function set_up($connection_name=null)
method test_within (line 23) | public function test_within()
method test_within_error_message (line 32) | public function test_within_error_message()
method test_within_custom_error_message (line 45) | public function test_within_custom_error_message()
method test_valid_in (line 60) | public function test_valid_in()
method test_aliased_size_of (line 69) | public function test_aliased_size_of()
method test_invalid_within_and_in (line 79) | public function test_invalid_within_and_in()
method test_valid_null (line 95) | public function test_valid_null()
method test_valid_blank (line 106) | public function test_valid_blank()
method test_invalid_blank (line 117) | public function test_invalid_blank()
method test_invalid_null_within (line 128) | public function test_invalid_null_within()
method test_invalid_null_minimum (line 139) | public function test_invalid_null_minimum()
method test_valid_null_maximum (line 151) | public function test_valid_null_maximum()
method test_float_as_impossible_range_option (line 161) | public function test_float_as_impossible_range_option()
method test_signed_integer_as_impossible_within_option (line 186) | public function test_signed_integer_as_impossible_within_option()
method test_not_array_as_impossible_range_option (line 202) | public function test_not_array_as_impossible_range_option()
method test_signed_integer_as_impossible_is_option (line 227) | public function test_signed_integer_as_impossible_is_option()
method test_lack_of_option (line 243) | public function test_lack_of_option()
method test_too_many_options (line 257) | public function test_too_many_options()
method test_too_many_options_with_different_option_types (line 274) | public function test_too_many_options_with_different_option_types()
method test_with_option_as_non_numeric (line 294) | public function test_with_option_as_non_numeric()
method test_with_option_as_non_numeric_non_array (line 306) | public function test_with_option_as_non_numeric_non_array()
method test_validates_length_of_maximum (line 315) | public function test_validates_length_of_maximum()
method test_validates_length_of_minimum (line 323) | public function test_validates_length_of_minimum()
method test_validates_length_of_min_max_custom_message (line 331) | public function test_validates_length_of_min_max_custom_message()
method test_validates_length_of_min_max_custom_message_overridden (line 344) | public function test_validates_length_of_min_max_custom_message_overri...
method test_validates_length_of_is (line 352) | public function test_validates_length_of_is()
FILE: test/ValidatesNumericalityOfTest.php
class BookNumericality (line 3) | class BookNumericality extends ActiveRecord\Model
class ValidatesNumericalityOfTest (line 12) | class ValidatesNumericalityOfTest extends DatabaseTest
method set_up (line 22) | public function set_up($connection_name=null)
method assert_validity (line 30) | private function assert_validity($value, $boolean, $msg=null)
method assert_invalid (line 50) | private function assert_invalid($values, $msg=null)
method assert_valid (line 56) | private function assert_valid($values, $msg=null)
method test_numericality (line 62) | public function test_numericality()
method test_not_anumber (line 70) | public function test_not_anumber()
method test_invalid_null (line 75) | public function test_invalid_null()
method test_invalid_blank (line 80) | public function test_invalid_blank()
method test_invalid_whitespace (line 85) | public function test_invalid_whitespace()
method test_valid_null (line 90) | public function test_valid_null()
method test_only_integer (line 96) | public function test_only_integer()
method test_only_integer_matching_does_not_ignore_other_options (line 104) | public function test_only_integer_matching_does_not_ignore_other_optio...
method test_greater_than (line 112) | public function test_greater_than()
method test_greater_than_or_equal_to (line 120) | public function test_greater_than_or_equal_to()
method test_less_than (line 128) | public function test_less_than()
method test_less_than_or_equal_to (line 136) | public function test_less_than_or_equal_to()
method test_greater_than_less_than_and_even (line 144) | public function test_greater_than_less_than_and_even()
method test_custom_message (line 152) | public function test_custom_message()
FILE: test/ValidatesPresenceOfTest.php
class BookPresence (line 3) | class BookPresence extends ActiveRecord\Model
class AuthorPresence (line 12) | class AuthorPresence extends ActiveRecord\Model
class ValidatesPresenceOfTest (line 21) | class ValidatesPresenceOfTest extends DatabaseTest
method test_presence (line 23) | public function test_presence()
method test_presence_on_date_field_is_valid (line 29) | public function test_presence_on_date_field_is_valid()
method test_presence_on_date_field_is_not_valid (line 35) | public function test_presence_on_date_field_is_not_valid()
method test_invalid_null (line 41) | public function test_invalid_null()
method test_invalid_blank (line 47) | public function test_invalid_blank()
method test_valid_white_space (line 53) | public function test_valid_white_space()
method test_custom_message (line 59) | public function test_custom_message()
method test_valid_zero (line 68) | public function test_valid_zero()
FILE: test/ValidationsTest.php
class BookValidations (line 5) | class BookValidations extends ActiveRecord\Model
method validate (line 14) | public function validate()
class ValuestoreValidations (line 21) | class ValuestoreValidations extends ActiveRecord\Model
class ValidationsTest (line 27) | class ValidationsTest extends DatabaseTest
method set_up (line 29) | public function set_up($connection_name=null)
method test_is_valid_invokes_validations (line 39) | public function test_is_valid_invokes_validations()
method test_is_valid_returns_true_if_no_validations_exist (line 47) | public function test_is_valid_returns_true_if_no_validations_exist()
method test_is_valid_returns_false_if_failed_validations (line 53) | public function test_is_valid_returns_false_if_failed_validations()
method test_is_invalid (line 59) | public function test_is_invalid()
method test_is_invalid_is_true (line 65) | public function test_is_invalid_is_true()
method test_is_iterable (line 71) | public function test_is_iterable()
method test_full_messages (line 80) | public function test_full_messages()
method test_to_array (line 88) | public function test_to_array()
method test_toString (line 96) | public function test_toString()
method test_validates_uniqueness_of (line 105) | public function test_validates_uniqueness_of()
method test_validates_uniqueness_of_excludes_self (line 114) | public function test_validates_uniqueness_of_excludes_self()
method test_validates_uniqueness_of_with_multiple_fields (line 120) | public function test_validates_uniqueness_of_with_multiple_fields()
method test_validates_uniqueness_of_with_multiple_fields_is_not_unique (line 128) | public function test_validates_uniqueness_of_with_multiple_fields_is_n...
method test_validates_uniqueness_of_works_with_alias_attribute (line 137) | public function test_validates_uniqueness_of_works_with_alias_attribute()
method test_validates_uniqueness_of_works_with_mysql_reserved_word_as_column_name (line 145) | public function test_validates_uniqueness_of_works_with_mysql_reserved...
method test_get_validation_rules (line 154) | public function test_get_validation_rules()
method test_model_is_nulled_out_to_prevent_memory_leak (line 160) | public function test_model_is_nulled_out_to_prevent_memory_leak()
method test_validations_takes_strings (line 167) | public function test_validations_takes_strings()
method test_gh131_custom_validation (line 174) | public function test_gh131_custom_validation()
FILE: test/helpers/AdapterTest.php
class AdapterTest (line 4) | class AdapterTest extends DatabaseTest
method set_up (line 8) | public function set_up($connection_name=null)
method test_i_has_a_default_port_unless_im_sqlite (line 17) | public function test_i_has_a_default_port_unless_im_sqlite()
method test_should_set_adapter_variables (line 26) | public function test_should_set_adapter_variables()
method test_null_connection_string_uses_default_connection (line 31) | public function test_null_connection_string_uses_default_connection()
method test_invalid_connection_protocol (line 41) | public function test_invalid_connection_protocol()
method test_no_host_connection (line 49) | public function test_no_host_connection()
method test_connection_failed_invalid_host (line 60) | public function test_connection_failed_invalid_host()
method test_connection_failed (line 71) | public function test_connection_failed()
method test_connect_failed (line 79) | public function test_connect_failed()
method test_connect_with_port (line 84) | public function test_connect_with_port()
method test_connect_to_invalid_database (line 105) | public function test_connect_to_invalid_database()
method test_date_time_type (line 110) | public function test_date_time_type()
method test_date (line 118) | public function test_date()
method test_columns_no_inflection_on_hash_key (line 126) | public function test_columns_no_inflection_on_hash_key()
method test_columns_nullable (line 132) | public function test_columns_nullable()
method test_columns_pk (line 139) | public function test_columns_pk()
method test_columns_sequence (line 146) | public function test_columns_sequence()
method test_columns_default (line 155) | public function test_columns_default()
method test_columns_type (line 161) | public function test_columns_type()
method test_columns_text (line 169) | public function test_columns_text()
method test_columns_time (line 176) | public function test_columns_time()
method test_query (line 183) | public function test_query()
method test_invalid_query (line 198) | public function test_invalid_query()
method test_fetch (line 203) | public function test_fetch()
method test_query_with_params (line 219) | public function test_query_with_params()
method test_insert_id_should_return_explicitly_inserted_id (line 233) | public function test_insert_id_should_return_explicitly_inserted_id()
method test_insert_id (line 239) | public function test_insert_id()
method test_insert_id_with_params (line 245) | public function test_insert_id_with_params()
method test_inflection (line 252) | public function test_inflection()
method test_escape (line 258) | public function test_escape()
method test_columnsx (line 264) | public function test_columnsx()
method test_columns_decimal (line 289) | public function test_columns_decimal()
method limit (line 296) | private function limit($offset, $limit)
method test_limit (line 304) | public function test_limit()
method test_limit_to_first_record (line 309) | public function test_limit_to_first_record()
method test_limit_to_last_record (line 314) | public function test_limit_to_last_record()
method test_limit_with_null_offset (line 319) | public function test_limit_with_null_offset()
method test_limit_with_nulls (line 324) | public function test_limit_with_nulls()
method test_fetch_no_results (line 329) | public function test_fetch_no_results()
method test_tables (line 335) | public function test_tables()
method test_query_column_info (line 340) | public function test_query_column_info()
method test_query_table_info (line 345) | public function test_query_table_info()
method test_query_table_info_must_return_one_field (line 350) | public function test_query_table_info_must_return_one_field()
method test_transaction_commit (line 356) | public function test_transaction_commit()
method test_transaction_rollback (line 367) | public function test_transaction_rollback()
method test_show_me_a_useful_pdo_exception_message (line 378) | public function test_show_me_a_useful_pdo_exception_message()
method test_quote_name_does_not_over_quote (line 388) | public function test_quote_name_does_not_over_quote()
method test_datetime_to_string (line 399) | public function test_datetime_to_string()
method test_date_to_string (line 405) | public function test_date_to_string()
FILE: test/helpers/DatabaseLoader.php
class DatabaseLoader (line 2) | class DatabaseLoader
method __construct (line 7) | public function __construct($db)
method reset_table_data (line 22) | public function reset_table_data()
method drop_tables (line 41) | public function drop_tables()
method exec_sql_script (line 69) | public function exec_sql_script($file)
method get_fixture_tables (line 78) | public function get_fixture_tables()
method get_sql (line 91) | public function get_sql($file)
method load_fixture_data (line 101) | public function load_fixture_data($table)
method quote_name (line 122) | public function quote_name($name)
FILE: test/helpers/DatabaseTest.php
class DatabaseTest (line 4) | class DatabaseTest extends SnakeCase_PHPUnit_Framework_TestCase
method set_up (line 10) | public function set_up($connection_name=null)
method tear_down (line 45) | public function tear_down()
method assert_exception_message_contains (line 52) | public function assert_exception_message_contains($contains, $closure)
method assert_sql_has (line 71) | public function assert_sql_has($needle, $haystack)
method assert_sql_doesnt_has (line 78) | public function assert_sql_doesnt_has($needle, $haystack)
FILE: test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php
class SnakeCase_PHPUnit_Framework_TestCase (line 2) | class SnakeCase_PHPUnit_Framework_TestCase extends PHPUnit_Framework_Tes...
method __call (line 4) | public function __call($meth, $args)
method setUp (line 16) | public function setUp()
method tearDown (line 22) | public function tearDown()
method setup_assert_keys (line 28) | private function setup_assert_keys($args)
method assert_has_keys (line 36) | public function assert_has_keys(/* $keys..., $array */)
method assert_doesnt_has_keys (line 46) | public function assert_doesnt_has_keys(/* $keys..., $array */)
method assert_is_a (line 54) | public function assert_is_a($expected_class, $object)
method assert_datetime_equals (line 59) | public function assert_datetime_equals($expected, $actual)
FILE: test/helpers/foo.php
class User (line 5) | class User extends \ActiveRecord\Model {
class Newsletter (line 13) | class Newsletter extends \ActiveRecord\Model {
class UserNewsletter (line 20) | class UserNewsletter extends \ActiveRecord\Model {
FILE: test/models/Amenity.php
class Amenity (line 2) | class Amenity extends ActiveRecord\Model
FILE: test/models/Author.php
class Author (line 2) | class Author extends ActiveRecord\Model
method set_password (line 13) | public function set_password($plaintext)
method set_name (line 18) | public function set_name($value)
method return_something (line 24) | public function return_something()
FILE: test/models/AuthorAttrAccessible.php
class AuthorAttrAccessible (line 2) | class AuthorAttrAccessible extends ActiveRecord\Model
FILE: test/models/AwesomePerson.php
class AwesomePerson (line 2) | class AwesomePerson extends ActiveRecord\Model
FILE: test/models/Book.php
class Book (line 2) | class Book extends ActiveRecord\Model
method upper_name (line 8) | public function upper_name()
method name (line 13) | public function name()
method get_name (line 18) | public function get_name()
method get_upper_name (line 26) | public function get_upper_name()
method get_lower_name (line 31) | public function get_lower_name()
FILE: test/models/BookAttrAccessible.php
class BookAttrAccessible (line 2) | class BookAttrAccessible extends ActiveRecord\Model
FILE: test/models/BookAttrProtected.php
class BookAttrProtected (line 2) | class BookAttrProtected extends ActiveRecord\Model
FILE: test/models/Employee.php
class Employee (line 2) | class Employee extends ActiveRecord\Model
FILE: test/models/Event.php
class Event (line 2) | class Event extends ActiveRecord\Model
FILE: test/models/Host.php
class Host (line 2) | class Host extends ActiveRecord\Model
FILE: test/models/JoinAuthor.php
class JoinAuthor (line 2) | class JoinAuthor extends ActiveRecord\Model
FILE: test/models/JoinBook.php
class JoinBook (line 2) | class JoinBook extends ActiveRecord\Model
FILE: test/models/NamespaceTest/Book.php
class Book (line 4) | class Book extends \ActiveRecord\Model
FILE: test/models/NamespaceTest/SubNamespaceTest/Page.php
class Page (line 4) | class Page extends \ActiveRecord\Model
FILE: test/models/Position.php
class Position (line 2) | class Position extends ActiveRecord\Model
FILE: test/models/Property.php
class Property (line 2) | class Property extends ActiveRecord\Model
FILE: test/models/PropertyAmenity.php
class PropertyAmenity (line 2) | class PropertyAmenity extends ActiveRecord\Model
FILE: test/models/Publisher.php
class Publisher (line 2) | class Publisher extends ActiveRecord\Model
FILE: test/models/RmBldg.php
class RmBldg (line 2) | class RmBldg extends ActiveRecord\Model
FILE: test/models/Venue.php
class Venue (line 2) | class Venue extends ActiveRecord\Model
method get_state (line 20) | public function get_state()
method set_state (line 28) | public function set_state($value)
FILE: test/models/VenueAfterCreate.php
class VenueAfterCreate (line 2) | class VenueAfterCreate extends ActiveRecord\Model
method change_name_after_create_if_name_is_change_me (line 8) | public function change_name_after_create_if_name_is_change_me()
FILE: test/models/VenueCB.php
class VenueCB (line 2) | class VenueCB extends ActiveRecord\Model
method after_construct (line 15) | public function after_construct() {}
method non_generic_after_construct (line 17) | public function non_generic_after_construct() {}
method after_destroy_one (line 19) | public function after_destroy_one() {}
method after_destroy_two (line 20) | public function after_destroy_two() {}
method before_destroy_using_string (line 22) | public function before_destroy_using_string() {}
method before_update_halt_execution (line 24) | public function before_update_halt_execution()
method before_destroy_halt_execution (line 29) | public function before_destroy_halt_execution()
method before_create_halt_execution (line 34) | public function before_create_halt_execution()
method before_validation_halt_execution (line 39) | public function before_validation_halt_execution()
FILE: test/sql/mysql.sql
type authors (line 1) | CREATE TABLE authors(
type books (line 16) | CREATE TABLE books(
type publishers (line 25) | CREATE TABLE publishers(
type venues (line 30) | CREATE TABLE venues (
type events (line 40) | CREATE TABLE events (
type hosts (line 49) | CREATE TABLE hosts(
type employees (line 54) | CREATE TABLE employees (
type positions (line 61) | CREATE TABLE positions (
type `rm-bldg` (line 68) | CREATE TABLE `rm-bldg`(
type awesome_people (line 74) | CREATE TABLE awesome_people(
type amenities (line 80) | CREATE TABLE amenities(
type property (line 85) | CREATE TABLE property(
type property_amenities (line 89) | CREATE TABLE property_amenities(
type users (line 95) | CREATE TABLE users (
type newsletters (line 99) | CREATE TABLE newsletters (
type user_newsletters (line 103) | CREATE TABLE user_newsletters (
type valuestore (line 109) | CREATE TABLE valuestore (
FILE: test/sql/oci.sql
type authors (line 2) | CREATE TABLE authors(
type books (line 17) | CREATE TABLE books(
type publishers (line 26) | CREATE TABLE publishers(
type venues (line 32) | CREATE TABLE venues (
type events (line 43) | CREATE TABLE events (
type hosts (line 53) | CREATE TABLE hosts(
type employees (line 59) | CREATE TABLE employees (
type positions (line 67) | CREATE TABLE positions (
type awesome_people (line 75) | CREATE TABLE awesome_people(
type amenities (line 82) | CREATE TABLE amenities(
type property (line 88) | CREATE TABLE property(
type property_amenities (line 93) | CREATE TABLE property_amenities(
type valuestore (line 100) | CREATE TABLE valuestore(
FILE: test/sql/pgsql.sql
type authors (line 1) | CREATE TABLE authors(
type books (line 15) | CREATE TABLE books(
type publishers (line 24) | CREATE TABLE publishers(
type venues (line 29) | CREATE TABLE venues (
type events (line 39) | CREATE TABLE events (
type hosts (line 48) | CREATE TABLE hosts(
type employees (line 53) | CREATE TABLE employees (
type positions (line 60) | CREATE TABLE positions (
type "rm-bldg" (line 67) | CREATE TABLE "rm-bldg"(
type awesome_people (line 73) | CREATE TABLE awesome_people(
type amenities (line 79) | CREATE TABLE amenities(
type property (line 84) | CREATE TABLE property(
type property_amenities (line 88) | CREATE TABLE property_amenities(
type users (line 94) | CREATE TABLE users(
type newsletters (line 98) | CREATE TABLE newsletters(
type user_newsletters (line 102) | CREATE TABLE user_newsletters(
type valuestore (line 108) | CREATE TABLE valuestore (
type user_newsletters_id_and_user_id_idx (line 115) | CREATE INDEX user_newsletters_id_and_user_id_idx ON user_newsletters USI...
FILE: test/sql/sqlite.sql
type authors (line 1) | CREATE TABLE authors(
type books (line 15) | CREATE TABLE books(
type publishers (line 24) | CREATE TABLE publishers(
type venues (line 29) | CREATE TABLE venues (
type events (line 39) | CREATE TABLE events (
type hosts (line 48) | CREATE TABLE hosts(
type employees (line 53) | CREATE TABLE employees (
type positions (line 60) | CREATE TABLE positions (
type `rm-bldg` (line 67) | CREATE TABLE `rm-bldg`(
type awesome_people (line 73) | CREATE TABLE awesome_people(
type amenities (line 79) | CREATE TABLE amenities(
type property (line 84) | CREATE TABLE property(
type property_amenities (line 88) | CREATE TABLE property_amenities(
type users (line 94) | CREATE TABLE users (
type newsletters (line 98) | CREATE TABLE newsletters (
type user_newsletters (line 102) | CREATE TABLE user_newsletters (
type valuestore (line 108) | CREATE TABLE valuestore (
Condensed preview — 126 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (495K chars).
[
{
"path": ".editorconfig",
"chars": 287,
"preview": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_"
},
{
"path": ".gitignore",
"chars": 82,
"preview": ".project\n.buildpath\n.settings\n*.log\n*.db\n*.swp\nvendor/*\ncomposer.lock\nphpunit.xml\n"
},
{
"path": ".travis.yml",
"chars": 935,
"preview": "install: composer install --prefer-source --dev\n\nservices:\n - memcache\n\nenv: PHPAR_MYSQL=mysql://root@127.0.0.1/phpar_t"
},
{
"path": "ActiveRecord.php",
"chars": 1597,
"preview": "<?php\r\nif (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)\r\n\tdie('PHP ActiveRecord requires PHP 5.3 or higher');\r\n"
},
{
"path": "CHANGELOG",
"chars": 1076,
"preview": "Version 1.0 - June 27, 2010\n\n- d2bed65 fixed an error with eager loading when no records exist\n- c225942 fixed set metho"
},
{
"path": "CONTRIBUTING.md",
"chars": 1559,
"preview": "# Contributing to PHP ActiveRecord #\n\nWe always appreciate contributions to PHP ActiveRecord, but we are not always able"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "Copyright (c) 2009\n\nAUTHORS:\nKien La\nJacques Fuentes\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 5586,
"preview": "# PHP ActiveRecord - Version 1.0 #\n\n[,\n author varchar(50)\n);\n\ninsert in"
},
{
"path": "examples/simple/simple_with_options.php",
"chars": 808,
"preview": "<?php\nrequire_once __DIR__ . '/../../ActiveRecord.php';\n\nclass Book extends ActiveRecord\\Model\n{\n\t// explicit table name"
},
{
"path": "examples/simple/simple_with_options.sql",
"chars": 162,
"preview": "create table simple_book(\n book_id int not null primary key auto_increment,\n name varchar(50)\n);\n\ninsert into simple_b"
},
{
"path": "lib/Cache.php",
"chars": 2841,
"preview": "<?php\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Cache::get('the-cache-key', function() {\n *\t # this gets executed whe"
},
{
"path": "lib/CallBack.php",
"chars": 8195,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Callbacks allow the programmer to ho"
},
{
"path": "lib/Column.php",
"chars": 4535,
"preview": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * Class for a table column.\r\n *\r\n * @package"
},
{
"path": "lib/Config.php",
"chars": 7913,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse Closure;\n\n/**\n * Manages configuration options for Ac"
},
{
"path": "lib/Connection.php",
"chars": 13428,
"preview": "<?php\n\n/**\n * @package ActiveRecord\n */\n\nnamespace ActiveRecord;\n\nrequire_once 'Column.php';\n\nuse PDO;\nuse PDOException;"
},
{
"path": "lib/ConnectionManager.php",
"chars": 1341,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Singleton to manage any and all database connecti"
},
{
"path": "lib/DateTime.php",
"chars": 5051,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * An extension of PHP's DateTime class to provide d"
},
{
"path": "lib/DateTimeInterface.php",
"chars": 1003,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Interface for the ActiveRecord\\DateTime class so "
},
{
"path": "lib/Exceptions.php",
"chars": 3348,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Generic base exception for all ActiveRecord speci"
},
{
"path": "lib/Expressions.php",
"chars": 4152,
"preview": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * Templating like class for building SQL sta"
},
{
"path": "lib/Inflector.php",
"chars": 2491,
"preview": "<?php\r\n/**\r\n * @package ActiveRecord\r\n */\r\nnamespace ActiveRecord;\r\n\r\n/**\r\n * @package ActiveRecord\r\n */\r\nabstract class"
},
{
"path": "lib/Model.php",
"chars": 52619,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * The base class for your models.\n *\n * Defining an"
},
{
"path": "lib/Reflections.php",
"chars": 1745,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse ReflectionClass;\n\n/**\n * Simple class that caches ref"
},
{
"path": "lib/Relationship.php",
"chars": 21176,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Interface for a table relationship.\n *\n * @packag"
},
{
"path": "lib/SQLBuilder.php",
"chars": 9248,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Helper class for building sql statements progmati"
},
{
"path": "lib/Serialization.php",
"chars": 8541,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\nuse XmlWriter;\n\n/**\n * Base class for Model serializers.\n"
},
{
"path": "lib/Singleton.php",
"chars": 1226,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * This implementation of the singleton pattern does"
},
{
"path": "lib/Table.php",
"chars": 15881,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Manages reading and writing to a database table.\n"
},
{
"path": "lib/Utils.php",
"chars": 9687,
"preview": "<?php\n/**\n *\n * @package ActiveRecord\n */\n\n/*\n * Thanks to http://www.eval.ca/articles/php-pluralize (MIT license)\n * "
},
{
"path": "lib/Validations.php",
"chars": 24555,
"preview": "<?php\n/**\n * These two classes have been <i>heavily borrowed</i> from Ruby on Rails' ActiveRecord so much that\n * this p"
},
{
"path": "lib/adapters/MysqlAdapter.php",
"chars": 2386,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Adapter for MySQL.\n *\n * @package ActiveRecord\n *"
},
{
"path": "lib/adapters/OciAdapter.php",
"chars": 3935,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\nuse PDO;\n\n/**\n * Adapter for OCI (not completed yet).\n *"
},
{
"path": "lib/adapters/PgsqlAdapter.php",
"chars": 3486,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\n/**\n * Adapter for Postgres (not completed yet)\n * \n * @"
},
{
"path": "lib/adapters/SqliteAdapter.php",
"chars": 2922,
"preview": "<?php\n/**\n * @package ActiveRecord\n */\nnamespace ActiveRecord;\n\nuse PDO;\n\n/**\n * Adapter for SQLite.\n *\n * @package Acti"
},
{
"path": "lib/cache/Memcache.php",
"chars": 1132,
"preview": "<?php\nnamespace ActiveRecord;\n\nclass Memcache\n{\n\tconst DEFAULT_PORT = 11211;\n\n\tprivate $memcache;\n\n\t/**\n\t * Creates a Me"
},
{
"path": "phpunit.xml.dist",
"chars": 540,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit backupGlobals=\"false\"\n backupStaticAttributes=\"false\"\n "
},
{
"path": "test/ActiveRecordCacheTest.php",
"chars": 1051,
"preview": "<?php\nuse ActiveRecord\\Cache;\n\nclass ActiveRecordCacheTest extends DatabaseTest\n{\n\tpublic function set_up($connection_na"
},
{
"path": "test/ActiveRecordFindTest.php",
"chars": 14651,
"preview": "<?php\n\nclass ActiveRecordFindTest extends DatabaseTest\n{\n\t/**\n\t * @expectedException ActiveRecord\\RecordNotFound\n\t */\n\tp"
},
{
"path": "test/ActiveRecordTest.php",
"chars": 16786,
"preview": "<?php\n\nclass ActiveRecordTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up("
},
{
"path": "test/ActiveRecordWriteTest.php",
"chars": 12298,
"preview": "<?php\nuse ActiveRecord\\DateTime;\n\nclass DirtyAuthor extends ActiveRecord\\Model\n{\n\tstatic $table = 'authors';\n\tstatic $be"
},
{
"path": "test/CacheModelTest.php",
"chars": 4088,
"preview": "<?php\nuse ActiveRecord\\Cache;\n\nclass CacheModelTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null"
},
{
"path": "test/CacheTest.php",
"chars": 1945,
"preview": "<?php\nuse ActiveRecord\\Cache;\n\nclass CacheTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()\n"
},
{
"path": "test/CallbackTest.php",
"chars": 9994,
"preview": "<?php\n\nclass CallBackTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up($con"
},
{
"path": "test/ColumnTest.php",
"chars": 4924,
"preview": "<?php\n\nuse ActiveRecord\\Column;\nuse ActiveRecord\\DateTime;\nuse ActiveRecord\\DatabaseException;\n\nclass ColumnTest extends"
},
{
"path": "test/ConfigTest.php",
"chars": 3900,
"preview": "<?php\n\nuse ActiveRecord\\Config;\nuse ActiveRecord\\ConfigException;\n\nclass TestLogger\n{\n\tprivate function log() {}\n}\n\nclas"
},
{
"path": "test/ConnectionManagerTest.php",
"chars": 1829,
"preview": "<?php\n\nuse ActiveRecord\\Config;\nuse ActiveRecord\\ConnectionManager;\n\nclass ConnectionManagerTest extends DatabaseTest\n{\n"
},
{
"path": "test/ConnectionTest.php",
"chars": 2817,
"preview": "<?php\nuse ActiveRecord\\Connection;\n\n\n// Only use this to test static methods in Connection that are not specific\n// to a"
},
{
"path": "test/DateFormatTest.php",
"chars": 353,
"preview": "<?php\n\nclass DateFormatTest extends DatabaseTest\n{\n\n\tpublic function test_datefield_gets_converted_to_ar_datetime()\n\t{\n\t"
},
{
"path": "test/DateTimeTest.php",
"chars": 6222,
"preview": "<?php\nuse ActiveRecord\\DatabaseException;\nuse ActiveRecord\\DateTime as DateTime;\n\nclass DateTimeTest extends SnakeCase_P"
},
{
"path": "test/ExpressionsTest.php",
"chars": 6103,
"preview": "<?php\nrequire_once __DIR__ . '/../lib/Expressions.php';\n\nuse ActiveRecord\\Expressions;\nuse ActiveRecord\\ConnectionManage"
},
{
"path": "test/HasManyThroughTest.php",
"chars": 1393,
"preview": "<?php\ninclude 'helpers/foo.php';\n\nuse foo\\bar\\biz\\User;\nuse foo\\bar\\biz\\Newsletter;\n\nclass HasManyThroughTest extends Da"
},
{
"path": "test/InflectorTest.php",
"chars": 751,
"preview": "<?php\nrequire_once __DIR__ . '/../lib/Inflector.php';\n\nclass InflectorTest extends SnakeCase_PHPUnit_Framework_TestCase\n"
},
{
"path": "test/ModelCallbackTest.php",
"chars": 3068,
"preview": "<?php\n\nclass ModelCallbackTest extends DatabaseTest\n{\n\tpublic function set_up($connection_name=null)\n\t{\n\t\tparent::set_up"
},
{
"path": "test/MysqlAdapterTest.php",
"chars": 1138,
"preview": "<?php\nuse ActiveRecord\\Column;\n\nrequire_once __DIR__ . '/../lib/adapters/MysqlAdapter.php';\n\nclass MysqlAdapterTest exte"
},
{
"path": "test/OciAdapterTest.php",
"chars": 1424,
"preview": "<?php\nrequire_once __DIR__ . '/../lib/adapters/OciAdapter.php';\n\nclass OciAdapterTest extends AdapterTest\n{\n\tpublic func"
},
{
"path": "test/PgsqlAdapterTest.php",
"chars": 1381,
"preview": "<?php\nuse ActiveRecord\\Column;\n\nrequire_once __DIR__ . '/../lib/adapters/PgsqlAdapter.php';\n\nclass PgsqlAdapterTest exte"
},
{
"path": "test/RelationshipTest.php",
"chars": 24960,
"preview": "<?php\n\nclass NotModel {};\n\nclass AuthorWithNonModelRelationship extends ActiveRecord\\Model\n{\n\tstatic $pk = 'id';\n\tstatic"
},
{
"path": "test/SQLBuilderTest.php",
"chars": 9698,
"preview": "<?php\n\nuse ActiveRecord\\SQLBuilder;\nuse ActiveRecord\\Table;\n\nclass SQLBuilderTest extends DatabaseTest\n{\n\tprotected $tab"
},
{
"path": "test/SerializationTest.php",
"chars": 6605,
"preview": "<?php\nrequire_once __DIR__ . '/../lib/Serialization.php';\n\nuse ActiveRecord\\DateTime;\n\nclass SerializationTest extends D"
},
{
"path": "test/SqliteAdapterTest.php",
"chars": 2157,
"preview": "<?php\nrequire_once __DIR__ . '/../lib/adapters/SqliteAdapter.php';\n\nclass SqliteAdapterTest extends AdapterTest\n{\n\tpubli"
},
{
"path": "test/UtilsTest.php",
"chars": 3760,
"preview": "<?php\n\nuse ActiveRecord as AR;\n\nclass UtilsTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\tpublic function set_up()"
},
{
"path": "test/ValidatesFormatOfTest.php",
"chars": 3043,
"preview": "<?php\r\n\r\nclass BookFormat extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tstatic $validates_format_of = array"
},
{
"path": "test/ValidatesInclusionAndExclusionOfTest.php",
"chars": 4333,
"preview": "<?php\r\n\r\nclass BookExclusion extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tpublic static $validates_exclusi"
},
{
"path": "test/ValidatesLengthOfTest.php",
"chars": 11168,
"preview": "<?php\r\n\r\nclass BookLength extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'books';\r\n\tstatic $validates_length_of = array"
},
{
"path": "test/ValidatesNumericalityOfTest.php",
"chars": 4616,
"preview": "<?php\n\nclass BookNumericality extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\n\tstatic $validates_numericali"
},
{
"path": "test/ValidatesPresenceOfTest.php",
"chars": 1724,
"preview": "<?php\r\n\r\nclass BookPresence extends ActiveRecord\\Model\r\n{\r\n\tstatic $table_name = 'books';\r\n\r\n\tstatic $validates_presence"
},
{
"path": "test/ValidationsTest.php",
"chars": 5739,
"preview": "<?php\n\nuse ActiveRecord as AR;\n\nclass BookValidations extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\tstati"
},
{
"path": "test/fixtures/amenities.csv",
"chars": 55,
"preview": "amenity_id, type\n1, \"Test #1\"\n2, \"Test #2\"\n3, \"Test #3\""
},
{
"path": "test/fixtures/authors.csv",
"chars": 119,
"preview": "author_id,parent_author_id,name,publisher_id\n1,3,\"Tito\",1\n2,2,\"George W. Bush\",1\n3,1,\"Bill Clinton\",2\n4,2,\"Uncle Bob\",3"
},
{
"path": "test/fixtures/awesome_people.csv",
"chars": 24,
"preview": "id,author_id\n1,1\n2,2\n3,3"
},
{
"path": "test/fixtures/books.csv",
"chars": 111,
"preview": "book_id,author_id,secondary_author_id,name,special\n1,1,2,\"Ancient Art of Main Tanking\",0\n2,2,2,\"Another Book\",0"
},
{
"path": "test/fixtures/employees.csv",
"chars": 106,
"preview": "id,first_name,last_name,nick_name\n1,\"michio\",\"kaku\",\"kakz\"\n2,\"jacques\",\"fuentes\",\"jax\"\n3,\"kien\",\"la\",\"kla\""
},
{
"path": "test/fixtures/events.csv",
"chars": 311,
"preview": "id,venue_id,host_id,title,description,type\n1,1,1,\"Monday Night Music Club feat. The Shivers\",\"\",\"Music\"\n2,2,2,\"Yeah Yeah"
},
{
"path": "test/fixtures/hosts.csv",
"chars": 75,
"preview": "id,name\n1,\"David Letterman\"\n2,\"Billy Crystal\"\n3,\"Jon Stewart\"\n4,\"Funny Guy\""
},
{
"path": "test/fixtures/newsletters.csv",
"chars": 5,
"preview": "id\n1\n"
},
{
"path": "test/fixtures/positions.csv",
"chars": 83,
"preview": "id,employee_id,title,active\n3,1,\"physicist\",0\n2,2,\"programmer\",1\n1,3,\"programmer\",1"
},
{
"path": "test/fixtures/property.csv",
"chars": 23,
"preview": "property_id\n28840\n28841"
},
{
"path": "test/fixtures/property_amenities.csv",
"chars": 96,
"preview": "id, amenity_id, property_id\n257117, 1, 28840\n257118, 2, 28840\n257119, 2, 28841\n257120, 3, 28841\n"
},
{
"path": "test/fixtures/publishers.csv",
"chars": 71,
"preview": "publisher_id,name\n1,\"Random House\"\n2,\"Houghton Mifflin\"\n3,\"Scholastic\"\n"
},
{
"path": "test/fixtures/rm-bldg.csv",
"chars": 38,
"preview": "rm-id,rm-name,\"space out\"\n1,\"name\",\"x\""
},
{
"path": "test/fixtures/user_newsletters.csv",
"chars": 31,
"preview": "id,user_id,newsletter_id\n1,1,1\n"
},
{
"path": "test/fixtures/users.csv",
"chars": 5,
"preview": "id\n1\n"
},
{
"path": "test/fixtures/valuestore.csv",
"chars": 0,
"preview": ""
},
{
"path": "test/fixtures/venues.csv",
"chars": 468,
"preview": "id,name,city,state,address,phone\n1,\"Blender Theater at Gramercy\",\"New York\",\"NY\",\"127 East 23rd Street\",\"2127776800\"\n2,\""
},
{
"path": "test/helpers/AdapterTest.php",
"chars": 11914,
"preview": "<?php\nuse ActiveRecord\\Column;\n\nclass AdapterTest extends DatabaseTest\n{\n\tconst InvalidDb = '__1337__invalid_db__';\n\n\tpu"
},
{
"path": "test/helpers/DatabaseLoader.php",
"chars": 2590,
"preview": "<?php\nclass DatabaseLoader\n{\n\tprivate $db;\n\tstatic $instances = array();\n\n\tpublic function __construct($db)\n\t{\n\t\t$this->"
},
{
"path": "test/helpers/DatabaseTest.php",
"chars": 2437,
"preview": "<?php\nrequire_once __DIR__ . '/DatabaseLoader.php';\n\nclass DatabaseTest extends SnakeCase_PHPUnit_Framework_TestCase\n{\n\t"
},
{
"path": "test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php",
"chars": 1738,
"preview": "<?php\nclass SnakeCase_PHPUnit_Framework_TestCase extends PHPUnit_Framework_TestCase\n{\n\tpublic function __call($meth, $ar"
},
{
"path": "test/helpers/config.php",
"chars": 2291,
"preview": "<?php\n\n/**\n * In order to run these unit tests, you need to install the required packages using Composer:\n *\n * $ com"
},
{
"path": "test/helpers/foo.php",
"chars": 508,
"preview": "<?php\n\nnamespace foo\\bar\\biz;\n\nclass User extends \\ActiveRecord\\Model {\n\tstatic $has_many = array(\n\t\tarray('user_newslet"
},
{
"path": "test/models/Amenity.php",
"chars": 182,
"preview": "<?php\nclass Amenity extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'amenities';\n\tstatic $primary_key = 'amenity_id';"
},
{
"path": "test/models/Author.php",
"chars": 845,
"preview": "<?php\nclass Author extends ActiveRecord\\Model\n{\n\tstatic $pk = 'author_id';\n//\tstatic $has_one = array(array('awesome_per"
},
{
"path": "test/models/AuthorAttrAccessible.php",
"chars": 544,
"preview": "<?php\nclass AuthorAttrAccessible extends ActiveRecord\\Model\n{\n\tstatic $pk = 'author_id';\n\tstatic $table_name = 'authors'"
},
{
"path": "test/models/AwesomePerson.php",
"chars": 99,
"preview": "<?php\nclass AwesomePerson extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array('author');\n}\n?>\n"
},
{
"path": "test/models/Book.php",
"chars": 627,
"preview": "<?php\nclass Book extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array('author');\n\tstatic $has_one = array();\n\tstatic"
},
{
"path": "test/models/BookAttrAccessible.php",
"chars": 213,
"preview": "<?php\nclass BookAttrAccessible extends ActiveRecord\\Model\n{\n\tstatic $pk = 'book_id';\n\tstatic $table_name = 'books';\n\n\tst"
},
{
"path": "test/models/BookAttrProtected.php",
"chars": 322,
"preview": "<?php\nclass BookAttrProtected extends ActiveRecord\\Model\n{\n\tstatic $pk = 'book_id';\n\tstatic $table_name = 'books';\n\tstat"
},
{
"path": "test/models/Employee.php",
"chars": 73,
"preview": "<?php\nclass Employee extends ActiveRecord\\Model\n{\n\tstatic $has_one;\n};\n?>"
},
{
"path": "test/models/Event.php",
"chars": 236,
"preview": "<?php\nclass Event extends ActiveRecord\\Model\n{\n\tstatic $belongs_to = array(\n\t\t'host',\n\t\t'venue'\n\t);\n\n\tstatic $delegate ="
},
{
"path": "test/models/Host.php",
"chars": 135,
"preview": "<?php\nclass Host extends ActiveRecord\\Model\n{\n\tstatic $has_many = array(\n\t\t'events',\n\t\tarray('venues', 'through' => 'eve"
},
{
"path": "test/models/JoinAuthor.php",
"chars": 117,
"preview": "<?php\nclass JoinAuthor extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'authors';\n\tstatic $pk = 'author_id';\n};\n?>"
},
{
"path": "test/models/JoinBook.php",
"chars": 118,
"preview": "<?php\nclass JoinBook extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'books';\n\n\tstatic $belongs_to = array();\n};\n?>"
},
{
"path": "test/models/NamespaceTest/Book.php",
"chars": 442,
"preview": "<?php\nnamespace NamespaceTest;\n\nclass Book extends \\ActiveRecord\\Model\n{\n\tstatic $belongs_to = array(\n\t\tarray('parent_bo"
},
{
"path": "test/models/NamespaceTest/SubNamespaceTest/Page.php",
"chars": 183,
"preview": "<?php\nnamespace NamespaceTest\\SubNamespaceTest;\n\nclass Page extends \\ActiveRecord\\Model\n{\n\tstatic $belong_to = array(\n\t\t"
},
{
"path": "test/models/Position.php",
"chars": 76,
"preview": "<?php\nclass Position extends ActiveRecord\\Model\n{\n\tstatic $belongs_to;\n};\n?>"
},
{
"path": "test/models/Property.php",
"chars": 240,
"preview": "<?php\nclass Property extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'property';\n\tstatic $primary_key = 'property_id'"
},
{
"path": "test/models/PropertyAmenity.php",
"chars": 196,
"preview": "<?php\nclass PropertyAmenity extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'property_amenities';\n\tstatic $primary_ke"
},
{
"path": "test/models/Publisher.php",
"chars": 213,
"preview": "<?php\nclass Publisher extends ActiveRecord\\Model\n{\n\tstatic $pk = 'publisher_id';\n\tstatic $cache = true;\n\tstatic $cache_e"
},
{
"path": "test/models/RmBldg.php",
"chars": 962,
"preview": "<?php\r\nclass RmBldg extends ActiveRecord\\Model\r\n{\r\n\tstatic $table = 'rm-bldg';\r\n\r\n\tstatic $validates_presence_of = array"
},
{
"path": "test/models/Venue.php",
"chars": 720,
"preview": "<?php\nclass Venue extends ActiveRecord\\Model\n{\n\tstatic $use_custom_get_state_getter = false;\n\tstatic $use_custom_set_sta"
},
{
"path": "test/models/VenueAfterCreate.php",
"chars": 334,
"preview": "<?php\nclass VenueAfterCreate extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'venues';\n\tstatic $after_create = array("
},
{
"path": "test/models/VenueCB.php",
"chars": 958,
"preview": "<?php\nclass VenueCB extends ActiveRecord\\Model\n{\n\tstatic $table_name = 'venues';\n\tstatic $before_save;\n\tstatic $before_u"
},
{
"path": "test/sql/mysql.sql",
"chars": 2696,
"preview": "CREATE TABLE authors(\n\tauthor_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tnam"
},
{
"path": "test/sql/oci-after-fixtures.sql",
"chars": 985,
"preview": "DROP SEQUENCE authors_seq;\nCREATE SEQUENCE authors_seq START WITH 100;\n\nDROP SEQUENCE books_seq;\nCREATE SEQUENCE books_s"
},
{
"path": "test/sql/oci.sql",
"chars": 2304,
"preview": "CREATE SEQUENCE authors_seq;\nCREATE TABLE authors(\n\tauthor_id INT NOT NULL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublishe"
},
{
"path": "test/sql/pgsql-after-fixtures.sql",
"chars": 1097,
"preview": "SELECT setval('authors_author_id_seq', max(author_id)) FROM authors;\nSELECT setval('books_book_id_seq', max(book_id)) FR"
},
{
"path": "test/sql/pgsql.sql",
"chars": 2301,
"preview": "CREATE TABLE authors(\n\tauthor_id SERIAL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR(25) NOT NUL"
},
{
"path": "test/sql/sqlite.sql",
"chars": 2441,
"preview": "CREATE TABLE authors(\n\tauthor_id INTEGER NOT NULL PRIMARY KEY,\n\tparent_author_id INT,\n\tpublisher_id INT,\n\tname VARCHAR "
}
]
About this extraction
This page contains the full source code of the kla/php-activerecord GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 126 files (439.1 KB), approximately 126.4k tokens, and a symbol index with 1324 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.