├── .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
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [*.php]
16 | indent_style = tab
17 | indent_size = 4
18 |
19 | [*.{xml,xml.dist}]
20 | indent_size = 4
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .buildpath
3 | .settings
4 | *.log
5 | *.db
6 | *.swp
7 | vendor/*
8 | composer.lock
9 | phpunit.xml
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | install: composer install --prefer-source --dev
2 |
3 | services:
4 | - memcache
5 |
6 | env: PHPAR_MYSQL=mysql://root@127.0.0.1/phpar_test PHPAR_PGSQL=pgsql://postgres@127.0.0.1/phpar_test
7 |
8 | language: php
9 | php:
10 | - 5.3
11 | - 5.4
12 | - 5.5
13 | - 7.0
14 | - 7.1
15 | - nightly
16 |
17 | matrix:
18 | include:
19 | - php: hhvm
20 | dist: trusty
21 | allow_failures:
22 | - php: hhvm
23 | - php: nightly
24 | fast_finish: true
25 |
26 | before_script:
27 | - |
28 | if [[ "${TRAVIS_PHP_VERSION:0:1}" == "7" ]]; then
29 | curl -L https://github.com/websupport-sk/pecl-memcache/archive/NON_BLOCKING_IO_php7.tar.gz | tar xz;
30 | (cd pecl-memcache-NON_BLOCKING_IO_php7 && phpize && ./configure && make && make install);
31 | fi
32 | - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo 'extension = "memcache.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi
33 |
34 | - mysql -e 'CREATE DATABASE phpar_test;'
35 | - psql -c 'CREATE DATABASE phpar_test;' -U postgres
36 |
37 | script: ./vendor/bin/phpunit
38 |
--------------------------------------------------------------------------------
/ActiveRecord.php:
--------------------------------------------------------------------------------
1 | get_model_directory();
33 | $root = realpath(isset($path) ? $path : '.');
34 |
35 | if (($namespaces = ActiveRecord\get_namespaces($class_name)))
36 | {
37 | $class_name = array_pop($namespaces);
38 | $directories = array();
39 |
40 | foreach ($namespaces as $directory)
41 | $directories[] = $directory;
42 |
43 | $root .= DIRECTORY_SEPARATOR . implode($directories, DIRECTORY_SEPARATOR);
44 | }
45 |
46 | $file = "$root/$class_name.php";
47 |
48 | if (file_exists($file))
49 | require_once $file;
50 | }
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | Version 1.0 - June 27, 2010
2 |
3 | - d2bed65 fixed an error with eager loading when no records exist
4 | - c225942 fixed set methods on DateTime objects to properly flag attributes as dirty
5 | - 46a1219 fixed a memory leak when using validations
6 | - c225942 fixed problem with some model functionality not working correctly after being deserialized
7 | - 3e26749 fixed validates_numericality_of to not ignore other options when only_integer is present and matches
8 | - 53ad5ec fixed ambiguous id error when finding by pk with a join option
9 | - 26e40f4 fixed conditions to accept DateTime values
10 | - 41e52fe changed serialization to serialize datetime fields as strings instead of the actual DateTime objects
11 | - dbee94b Model::transaction() now returns true if commit was successful otherwise false
12 |
13 | Versio 1.0 RC1 - May 7, 2010
14 |
15 | - support for Oracle
16 | - support for PostgreSQL
17 | - added delegators
18 | - added setters
19 | - added getters
20 | - added HAVING as a finder option
21 | - added ability to find using a hash
22 | - added find_or_create_by
23 | - added validates_uniqueness_of
24 | - added dynamic count_by
25 | - added eager loading
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to PHP ActiveRecord #
2 |
3 | We always appreciate contributions to PHP ActiveRecord, but we are not always able to respond as quickly as we would like.
4 | Please do not take delays personal and feel free to remind us by commenting on issues.
5 |
6 | ### Testing ###
7 |
8 | PHP ActiveRecord has a full set of unit tests, which are run by PHPUnit.
9 |
10 | In order to run these unit tests, you need to install the required packages using [Composer](https://getcomposer.org/):
11 |
12 | ```sh
13 | composer install
14 | ```
15 |
16 | After that you can run the tests by invoking the local PHPUnit
17 |
18 | To run all test simply use:
19 |
20 | ```sh
21 | vendor/bin/phpunit
22 | ```
23 |
24 | Or run a single test file by specifying its path:
25 |
26 | ```sh
27 | vendor/bin/phpunit test/InflectorTest.php
28 | ```
29 |
30 | #### Skipped Tests ####
31 |
32 | You might notice that some tests are marked as skipped. To obtain more information about skipped
33 | tests, pass the `--verbose` flag to PHPUnit:
34 |
35 | ```sh
36 | vendor/bin/phpunit --verbose
37 | ```
38 |
39 | Some common steps for fixing skipped tests are to:
40 |
41 | * Install `memcached` and the PHP memcached extension (e.g., `brew install php56-memcache memcached` on macOS)
42 | * Install the PDO drivers for PostgreSQL (e.g., `brew install php56-pdo-pgsql` on macOS)
43 | * 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.
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009
2 |
3 | AUTHORS:
4 | Kien La
5 | Jacques Fuentes
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP ActiveRecord - Version 1.0 #
2 |
3 | [](https://travis-ci.org/jpfuentes2/php-activerecord)
4 |
5 | by
6 |
7 | * [@kla](https://github.com/kla) - Kien La
8 | * [@jpfuentes2](https://github.com/jpfuentes2) - Jacques Fuentes
9 | * [And these terrific Contributors](https://github.com/kla/php-activerecord/contributors)
10 |
11 |
12 |
13 | ## Introduction ##
14 | A brief summarization of what ActiveRecord is:
15 |
16 | > Active record is an approach to access data in a database. A database table or view is wrapped into a class,
17 | > thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to
18 | > the table upon save. Any object loaded gets its information from the database; when an object is updated, the
19 | > corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for
20 | > each column in the table or view.
21 |
22 | More details can be found [here](http://en.wikipedia.org/wiki/Active_record_pattern).
23 |
24 | This implementation is inspired and thus borrows heavily from Ruby on Rails' ActiveRecord.
25 | We have tried to maintain their conventions while deviating mainly because of convenience or necessity.
26 | Of course, there are some differences which will be obvious to the user if they are familiar with rails.
27 |
28 | ## Minimum Requirements ##
29 |
30 | - PHP 5.3+
31 | - PDO driver for your respective database
32 |
33 | ## Supported Databases ##
34 |
35 | - MySQL
36 | - SQLite
37 | - PostgreSQL
38 | - Oracle
39 |
40 | ## Features ##
41 |
42 | - Finder methods
43 | - Dynamic finder methods
44 | - Writer methods
45 | - Relationships
46 | - Validations
47 | - Callbacks
48 | - Serializations (json/xml)
49 | - Transactions
50 | - Support for multiple adapters
51 | - Miscellaneous options such as: aliased/protected/accessible attributes
52 |
53 | ## Installation ##
54 |
55 | Setup is very easy and straight-forward. There are essentially only three configuration points you must concern yourself with:
56 |
57 | 1. Setting the model autoload directory.
58 | 2. Configuring your database connections.
59 | 3. Setting the database connection to use for your environment.
60 |
61 | Example:
62 |
63 | ```php
64 | ActiveRecord\Config::initialize(function($cfg)
65 | {
66 | $cfg->set_model_directory('/path/to/your/model_directory');
67 | $cfg->set_connections(
68 | array(
69 | 'development' => 'mysql://username:password@localhost/development_database_name',
70 | 'test' => 'mysql://username:password@localhost/test_database_name',
71 | 'production' => 'mysql://username:password@localhost/production_database_name'
72 | )
73 | );
74 | });
75 | ```
76 |
77 | Alternatively (w/o the 5.3 closure):
78 |
79 | ```php
80 | $cfg = ActiveRecord\Config::instance();
81 | $cfg->set_model_directory('/path/to/your/model_directory');
82 | $cfg->set_connections(
83 | array(
84 | 'development' => 'mysql://username:password@localhost/development_database_name',
85 | 'test' => 'mysql://username:password@localhost/test_database_name',
86 | 'production' => 'mysql://username:password@localhost/production_database_name'
87 | )
88 | );
89 | ```
90 |
91 | PHP ActiveRecord will default to use your development database. For testing or production, you simply set the default
92 | connection according to your current environment ('test' or 'production'):
93 |
94 | ```php
95 | ActiveRecord\Config::initialize(function($cfg)
96 | {
97 | $cfg->set_default_connection(your_environment);
98 | });
99 | ```
100 |
101 | Once you have configured these three settings you are done. ActiveRecord takes care of the rest for you.
102 | It does not require that you map your table schema to yaml/xml files. It will query the database for this information and
103 | cache it so that it does not make multiple calls to the database for a single schema.
104 |
105 | ## Basic CRUD ##
106 |
107 | ### Retrieve ###
108 | These are your basic methods to find and retrieve records from your database.
109 | See the *Finders* section for more details.
110 |
111 | ```php
112 | $post = Post::find(1);
113 | echo $post->title; # 'My first blog post!!'
114 | echo $post->author_id; # 5
115 |
116 | # also the same since it is the first record in the db
117 | $post = Post::first();
118 |
119 | # finding using dynamic finders
120 | $post = Post::find_by_name('The Decider');
121 | $post = Post::find_by_name_and_id('The Bridge Builder',100);
122 | $post = Post::find_by_name_or_id('The Bridge Builder',100);
123 |
124 | # finding using a conditions array
125 | $posts = Post::find('all',array('conditions' => array('name=? or id > ?','The Bridge Builder',100)));
126 | ```
127 |
128 | ### Create ###
129 | Here we create a new post by instantiating a new object and then invoking the save() method.
130 |
131 | ```php
132 | $post = new Post();
133 | $post->title = 'My first blog post!!';
134 | $post->author_id = 5;
135 | $post->save();
136 | # INSERT INTO `posts` (title,author_id) VALUES('My first blog post!!', 5)
137 | ```
138 |
139 | ### Update ###
140 | To update you would just need to find a record first and then change one of its attributes.
141 | It keeps an array of attributes that are "dirty" (that have been modified) and so our
142 | sql will only update the fields modified.
143 |
144 | ```php
145 | $post = Post::find(1);
146 | echo $post->title; # 'My first blog post!!'
147 | $post->title = 'Some real title';
148 | $post->save();
149 | # UPDATE `posts` SET title='Some real title' WHERE id=1
150 |
151 | $post->title = 'New real title';
152 | $post->author_id = 1;
153 | $post->save();
154 | # UPDATE `posts` SET title='New real title', author_id=1 WHERE id=1
155 | ```
156 |
157 | ### Delete ###
158 | Deleting a record will not *destroy* the object. This means that it will call sql to delete
159 | the record in your database but you can still use the object if you need to.
160 |
161 | ```php
162 | $post = Post::find(1);
163 | $post->delete();
164 | # DELETE FROM `posts` WHERE id=1
165 | echo $post->title; # 'New real title'
166 | ```
167 |
168 | ## Contributing ##
169 |
170 | Please refer to [CONTRIBUTING.md](https://github.com/jpfuentes2/php-activerecord/blob/master/CONTRIBUTING.md) for information on how to contribute to PHP ActiveRecord.
171 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-activerecord/php-activerecord",
3 | "type": "library",
4 | "description": "php-activerecord is an open source ORM library based on the ActiveRecord pattern.",
5 | "keywords": ["activerecord", "orm"],
6 | "homepage": "http://www.phpactiverecord.org/",
7 | "license": "MIT",
8 | "require": {
9 | "php": ">=5.3.0"
10 | },
11 | "require-dev": {
12 | "phpunit/phpunit": "4.*",
13 | "pear/pear_exception": "1.0-beta1",
14 | "pear/log": "~1.12"
15 | },
16 | "autoload": {
17 | "files": [ "ActiveRecord.php" ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/orders/models/Order.php:
--------------------------------------------------------------------------------
1 | 'payments',
14 | 'select' => 'people.*, payments.amount',
15 | 'conditions' => 'payments.amount < 200'));
16 |
17 | // order must have a price and tax > 0
18 | static $validates_numericality_of = array(
19 | array('price', 'greater_than' => 0),
20 | array('tax', 'greater_than' => 0));
21 |
22 | // setup a callback to automatically apply a tax
23 | static $before_validation_on_create = array('apply_tax');
24 |
25 | public function apply_tax()
26 | {
27 | if ($this->person->state == 'VA')
28 | $tax = 0.045;
29 | elseif ($this->person->state == 'CA')
30 | $tax = 0.10;
31 | else
32 | $tax = 0.02;
33 |
34 | $this->tax = $this->price * $tax;
35 | }
36 | }
37 | ?>
38 |
--------------------------------------------------------------------------------
/examples/orders/models/Payment.php:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/examples/orders/models/Person.php:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/examples/orders/orders.php:
--------------------------------------------------------------------------------
1 | set_model_directory(__DIR__ . '/models');
8 | $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/orders_test'));
9 |
10 | // you can change the default connection with the below
11 | //$cfg->set_default_connection('production');
12 | });
13 |
14 | // create some people
15 | $jax = new Person(array('name' => 'Jax', 'state' => 'CA'));
16 | $jax->save();
17 |
18 | // compact way to create and save a model
19 | $tito = Person::create(array('name' => 'Tito', 'state' => 'VA'));
20 |
21 | // place orders. tax is automatically applied in a callback
22 | // create_orders will automatically place the created model into $tito->orders
23 | // even if it failed validation
24 | $pokemon = $tito->create_orders(array('item_name' => 'Live Pokemon', 'price' => 6999.99));
25 | $coal = $tito->create_orders(array('item_name' => 'Lump of Coal', 'price' => 100.00));
26 | $freebie = $tito->create_orders(array('item_name' => 'Freebie', 'price' => -100.99));
27 |
28 | if (count($freebie->errors) > 0)
29 | echo "[FAILED] saving order $freebie->item_name: " . join(', ',$freebie->errors->full_messages()) . "\n\n";
30 |
31 | // payments
32 | $pokemon->create_payments(array('amount' => 1.99, 'person_id' => $tito->id));
33 | $pokemon->create_payments(array('amount' => 4999.50, 'person_id' => $tito->id));
34 | $pokemon->create_payments(array('amount' => 2.50, 'person_id' => $jax->id));
35 |
36 | // reload since we don't want the freebie to show up (because it failed validation)
37 | $tito->reload();
38 |
39 | echo "$tito->name has " . count($tito->orders) . " orders for: " . join(', ',ActiveRecord\collect($tito->orders,'item_name')) . "\n\n";
40 |
41 | // get all orders placed by Tito
42 | foreach (Order::find_all_by_person_id($tito->id) as $order)
43 | {
44 | echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax) ordered by " . $order->person->name . "\n";
45 |
46 | if (count($order->payments) > 0)
47 | {
48 | // display each payment for this order
49 | foreach ($order->payments as $payment)
50 | echo " payment #$payment->id of $$payment->amount by " . $payment->person->name . "\n";
51 | }
52 | else
53 | echo " no payments\n";
54 |
55 | echo "\n";
56 | }
57 |
58 | // display summary of all payments made by Tito and Jax
59 | $conditions = array(
60 | 'conditions' => array('id IN(?)',array($tito->id,$jax->id)),
61 | 'order' => 'name desc');
62 |
63 | foreach (Person::all($conditions) as $person)
64 | {
65 | $n = count($person->payments);
66 | $total = array_sum(ActiveRecord\collect($person->payments,'amount'));
67 | echo "$person->name made $n payments for a total of $$total\n\n";
68 | }
69 |
70 | // using order has_many people through payments with options
71 | // array('people', 'through' => 'payments', 'select' => 'people.*, payments.amount', 'conditions' => 'payments.amount < 200'));
72 | // this means our people in the loop below also has the payment information since it is part of an inner join
73 | // we will only see 2 of the people instead of 3 because 1 of the payments is greater than 200
74 | $order = Order::find($pokemon->id);
75 | echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax)\n";
76 |
77 | foreach ($order->people as $person)
78 | echo " payment of $$person->amount by " . $person->name . "\n";
79 | ?>
80 |
--------------------------------------------------------------------------------
/examples/orders/orders.sql:
--------------------------------------------------------------------------------
1 | -- written for mysql, not tested with any other db
2 |
3 | drop table if exists people;
4 | create table people(
5 | id int not null primary key auto_increment,
6 | name varchar(50),
7 | state char(2),
8 | created_at datetime,
9 | updated_at datetime
10 | );
11 |
12 | drop table if exists orders;
13 | create table orders(
14 | id int not null primary key auto_increment,
15 | person_id int not null,
16 | item_name varchar(50),
17 | price decimal(10,2),
18 | tax decimal(10,2),
19 | created_at datetime
20 | );
21 |
22 | drop table if exists payments;
23 | create table payments(
24 | id int not null primary key auto_increment,
25 | order_id int not null,
26 | person_id int not null,
27 | amount decimal(10,2),
28 | created_at datetime
29 | );
30 |
--------------------------------------------------------------------------------
/examples/simple/simple.php:
--------------------------------------------------------------------------------
1 | set_model_directory('.');
13 | $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/test'));
14 | });
15 |
16 | print_r(Book::first()->attributes());
17 | ?>
18 |
--------------------------------------------------------------------------------
/examples/simple/simple.sql:
--------------------------------------------------------------------------------
1 | create table books(
2 | id int not null primary key auto_increment,
3 | name varchar(50),
4 | author varchar(50)
5 | );
6 |
7 | insert into books(name,author) values('How to be Angry','Jax');
8 |
--------------------------------------------------------------------------------
/examples/simple/simple_with_options.php:
--------------------------------------------------------------------------------
1 | db.table_name
16 | static $db = 'test';
17 | }
18 |
19 | $connections = array(
20 | 'development' => 'mysql://invalid',
21 | 'production' => 'mysql://test:test@127.0.0.1/test'
22 | );
23 |
24 | // initialize ActiveRecord
25 | ActiveRecord\Config::initialize(function($cfg) use ($connections)
26 | {
27 | $cfg->set_model_directory('.');
28 | $cfg->set_connections($connections);
29 | });
30 |
31 | print_r(Book::first()->attributes());
32 | ?>
33 |
--------------------------------------------------------------------------------
/examples/simple/simple_with_options.sql:
--------------------------------------------------------------------------------
1 | create table simple_book(
2 | book_id int not null primary key auto_increment,
3 | name varchar(50)
4 | );
5 |
6 | insert into simple_book (name) values ('simple w/ options!');
7 |
--------------------------------------------------------------------------------
/lib/Cache.php:
--------------------------------------------------------------------------------
1 | set_cache('memcache://localhost:11211',array('namespace' => 'my_cool_app',
29 | * 'expire' => 120
30 | * ));
31 | *
32 | * In the example above all the keys expire after 120 seconds, and the
33 | * all get a postfix 'my_cool_app'.
34 | *
35 | * (Note: expiring needs to be implemented in your cache store.)
36 | *
37 | * @param string $url URL to your cache server
38 | * @param array $options Specify additional options
39 | */
40 | public static function initialize($url, $options=array())
41 | {
42 | if ($url)
43 | {
44 | $url = parse_url($url);
45 | $file = ucwords(Inflector::instance()->camelize($url['scheme']));
46 | $class = "ActiveRecord\\$file";
47 | require_once __DIR__ . "/cache/$file.php";
48 | static::$adapter = new $class($url);
49 | }
50 | else
51 | static::$adapter = null;
52 |
53 | static::$options = array_merge(array('expire' => 30, 'namespace' => ''),$options);
54 | }
55 |
56 | public static function flush()
57 | {
58 | if (static::$adapter)
59 | static::$adapter->flush();
60 | }
61 |
62 | /**
63 | * Attempt to retrieve a value from cache using a key. If the value is not found, then the closure method
64 | * will be invoked, and the result will be stored in cache using that key.
65 | * @param $key
66 | * @param $closure
67 | * @param $expire in seconds
68 | * @return mixed
69 | */
70 | public static function get($key, $closure, $expire=null)
71 | {
72 | if (!static::$adapter)
73 | return $closure();
74 |
75 | if (is_null($expire))
76 | {
77 | $expire = static::$options['expire'];
78 | }
79 |
80 | $key = static::get_namespace() . $key;
81 |
82 | if (!($value = static::$adapter->read($key)))
83 | static::$adapter->write($key, ($value = $closure()), $expire);
84 |
85 | return $value;
86 | }
87 |
88 | public static function set($key, $var, $expire=null)
89 | {
90 | if (!static::$adapter)
91 | return;
92 |
93 | if (is_null($expire))
94 | {
95 | $expire = static::$options['expire'];
96 | }
97 |
98 | $key = static::get_namespace() . $key;
99 | return static::$adapter->write($key, $var, $expire);
100 | }
101 |
102 | public static function delete($key)
103 | {
104 | if (!static::$adapter)
105 | return;
106 |
107 | $key = static::get_namespace() . $key;
108 | return static::$adapter->delete($key);
109 | }
110 |
111 | private static function get_namespace()
112 | {
113 | return (isset(static::$options['namespace']) && strlen(static::$options['namespace']) > 0) ? (static::$options['namespace'] . "::") : "";
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/CallBack.php:
--------------------------------------------------------------------------------
1 |
16 | *
after_construct: called after a model has been constructed
17 | * before_save: called before a model is saved
18 | * after_save: called after a model is saved
19 | * before_create: called before a NEW model is to be inserted into the database
20 | * after_create: called after a NEW model has been inserted into the database
21 | * before_update: called before an existing model has been saved
22 | * after_update: called after an existing model has been saved
23 | * before_validation: called before running validators
24 | * after_validation: called after running validators
25 | * before_validation_on_create: called before validation on a NEW model being inserted
26 | * after_validation_on_create: called after validation on a NEW model being inserted
27 | * before_validation_on_update: see above except for an existing model being saved
28 | * after_validation_on_update: ...
29 | * before_destroy: called after a model has been deleted
30 | * after_destroy: called after a model has been deleted
31 | *
32 | *
33 | * This class isn't meant to be used directly. Callbacks are defined on your model like the example below:
34 | *
35 | *
36 | * class Person extends ActiveRecord\Model {
37 | * static $before_save = array('make_name_uppercase');
38 | * static $after_save = array('do_happy_dance');
39 | *
40 | * public function make_name_uppercase() {
41 | * $this->name = strtoupper($this->name);
42 | * }
43 | *
44 | * public function do_happy_dance() {
45 | * happy_dance();
46 | * }
47 | * }
48 | *
49 | *
50 | * Available options for callbacks:
51 | *
52 | *
53 | * - prepend: puts the callback at the top of the callback chain instead of the bottom
54 | *
55 | *
56 | * @package ActiveRecord
57 | * @link http://www.phpactiverecord.org/guides/callbacks
58 | */
59 | class CallBack
60 | {
61 | /**
62 | * List of available callbacks.
63 | *
64 | * @var array
65 | */
66 | static protected $VALID_CALLBACKS = array(
67 | 'after_construct',
68 | 'before_save',
69 | 'after_save',
70 | 'before_create',
71 | 'after_create',
72 | 'before_update',
73 | 'after_update',
74 | 'before_validation',
75 | 'after_validation',
76 | 'before_validation_on_create',
77 | 'after_validation_on_create',
78 | 'before_validation_on_update',
79 | 'after_validation_on_update',
80 | 'before_destroy',
81 | 'after_destroy'
82 | );
83 |
84 | /**
85 | * Container for reflection class of given model
86 | *
87 | * @var object
88 | */
89 | private $klass;
90 |
91 | /**
92 | * List of public methods of the given model
93 | * @var array
94 | */
95 | private $publicMethods;
96 |
97 | /**
98 | * Holds data for registered callbacks.
99 | *
100 | * @var array
101 | */
102 | private $registry = array();
103 |
104 | /**
105 | * Creates a CallBack.
106 | *
107 | * @param string $model_class_name The name of a {@link Model} class
108 | * @return CallBack
109 | */
110 | public function __construct($model_class_name)
111 | {
112 | $this->klass = Reflections::instance()->get($model_class_name);
113 |
114 | foreach (static::$VALID_CALLBACKS as $name)
115 | {
116 | // look for explicitly defined static callback
117 | if (($definition = $this->klass->getStaticPropertyValue($name,null)))
118 | {
119 | if (!is_array($definition))
120 | $definition = array($definition);
121 |
122 | foreach ($definition as $method_name)
123 | $this->register($name,$method_name);
124 | }
125 |
126 | // implicit callbacks that don't need to have a static definition
127 | // simply define a method named the same as something in $VALID_CALLBACKS
128 | // and the callback is auto-registered
129 | elseif ($this->klass->hasMethod($name))
130 | $this->register($name,$name);
131 | }
132 | }
133 |
134 | /**
135 | * Returns all the callbacks registered for a callback type.
136 | *
137 | * @param $name string Name of a callback (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
138 | * @return array array of callbacks or null if invalid callback name.
139 | */
140 | public function get_callbacks($name)
141 | {
142 | return isset($this->registry[$name]) ? $this->registry[$name] : null;
143 | }
144 |
145 | /**
146 | * Invokes a callback.
147 | *
148 | * @internal This is the only piece of the CallBack class that carries its own logic for the
149 | * model object. For (after|before)_(create|update) callbacks, it will merge with
150 | * a generic 'save' callback which is called first for the lease amount of precision.
151 | *
152 | * @param string $model Model to invoke the callback on.
153 | * @param string $name Name of the callback to invoke
154 | * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
155 | * @return mixed null if $name was not a valid callback type or false if a method was invoked
156 | * that was for a before_* callback and that method returned false. If this happens, execution
157 | * of any other callbacks after the offending callback will not occur.
158 | */
159 | public function invoke($model, $name, $must_exist=true)
160 | {
161 | if ($must_exist && !array_key_exists($name, $this->registry))
162 | throw new ActiveRecordException("No callbacks were defined for: $name on " . get_class($model));
163 |
164 | // if it doesn't exist it might be a /(after|before)_(create|update)/ so we still need to run the save
165 | // callback
166 | if (!array_key_exists($name, $this->registry))
167 | $registry = array();
168 | else
169 | $registry = $this->registry[$name];
170 |
171 | $first = substr($name,0,6);
172 |
173 | // starts with /(after|before)_(create|update)/
174 | if (($first == 'after_' || $first == 'before') && (($second = substr($name,7,5)) == 'creat' || $second == 'updat' || $second == 'reate' || $second == 'pdate'))
175 | {
176 | $temporal_save = str_replace(array('create', 'update'), 'save', $name);
177 |
178 | if (!isset($this->registry[$temporal_save]))
179 | $this->registry[$temporal_save] = array();
180 |
181 | $registry = array_merge($this->registry[$temporal_save], $registry ? $registry : array());
182 | }
183 |
184 | if ($registry)
185 | {
186 | foreach ($registry as $method)
187 | {
188 | $ret = ($method instanceof Closure ? $method($model) : $model->$method());
189 |
190 | if (false === $ret && $first === 'before')
191 | return false;
192 | }
193 | }
194 | return true;
195 | }
196 |
197 | /**
198 | * Register a new callback.
199 | *
200 | * The option array can contain the following parameters:
201 | *
202 | * - prepend: Add this callback at the beginning of the existing callbacks (true) or at the end (false, default)
203 | *
204 | *
205 | * @param string $name Name of callback type (see {@link VALID_CALLBACKS $VALID_CALLBACKS})
206 | * @param mixed $closure_or_method_name Either a closure or the name of a method on the {@link Model}
207 | * @param array $options Options array
208 | * @return void
209 | * @throws ActiveRecordException if invalid callback type or callback method was not found
210 | */
211 | public function register($name, $closure_or_method_name=null, $options=array())
212 | {
213 | $options = array_merge(array('prepend' => false), $options);
214 |
215 | if (!$closure_or_method_name)
216 | $closure_or_method_name = $name;
217 |
218 | if (!in_array($name,self::$VALID_CALLBACKS))
219 | throw new ActiveRecordException("Invalid callback: $name");
220 |
221 | if (!($closure_or_method_name instanceof Closure))
222 | {
223 | if (!isset($this->publicMethods))
224 | $this->publicMethods = get_class_methods($this->klass->getName());
225 |
226 | if (!in_array($closure_or_method_name, $this->publicMethods))
227 | {
228 | if ($this->klass->hasMethod($closure_or_method_name))
229 | {
230 | // Method is private or protected
231 | throw new ActiveRecordException("Callback methods need to be public (or anonymous closures). " .
232 | "Please change the visibility of " . $this->klass->getName() . "->" . $closure_or_method_name . "()");
233 | }
234 | else
235 | {
236 | // i'm a dirty ruby programmer
237 | throw new ActiveRecordException("Unknown method for callback: $name" .
238 | (is_string($closure_or_method_name) ? ": #$closure_or_method_name" : ""));
239 | }
240 | }
241 | }
242 |
243 | if (!isset($this->registry[$name]))
244 | $this->registry[$name] = array();
245 |
246 | if ($options['prepend'])
247 | array_unshift($this->registry[$name], $closure_or_method_name);
248 | else
249 | $this->registry[$name][] = $closure_or_method_name;
250 | }
251 | }
--------------------------------------------------------------------------------
/lib/Column.php:
--------------------------------------------------------------------------------
1 | self::DATETIME,
29 | 'timestamp' => self::DATETIME,
30 | 'date' => self::DATE,
31 | 'time' => self::TIME,
32 |
33 | 'tinyint' => self::INTEGER,
34 | 'smallint' => self::INTEGER,
35 | 'mediumint' => self::INTEGER,
36 | 'int' => self::INTEGER,
37 | 'bigint' => self::INTEGER,
38 |
39 | 'float' => self::DECIMAL,
40 | 'double' => self::DECIMAL,
41 | 'numeric' => self::DECIMAL,
42 | 'decimal' => self::DECIMAL,
43 | 'dec' => self::DECIMAL);
44 |
45 | /**
46 | * The true name of this column.
47 | * @var string
48 | */
49 | public $name;
50 |
51 | /**
52 | * The inflected name of this columns .. hyphens/spaces will be => _.
53 | * @var string
54 | */
55 | public $inflected_name;
56 |
57 | /**
58 | * The type of this column: STRING, INTEGER, ...
59 | * @var integer
60 | */
61 | public $type;
62 |
63 | /**
64 | * The raw database specific type.
65 | * @var string
66 | */
67 | public $raw_type;
68 |
69 | /**
70 | * The maximum length of this column.
71 | * @var int
72 | */
73 | public $length;
74 |
75 | /**
76 | * True if this column allows null.
77 | * @var boolean
78 | */
79 | public $nullable;
80 |
81 | /**
82 | * True if this column is a primary key.
83 | * @var boolean
84 | */
85 | public $pk;
86 |
87 | /**
88 | * The default value of the column.
89 | * @var mixed
90 | */
91 | public $default;
92 |
93 | /**
94 | * True if this column is set to auto_increment.
95 | * @var boolean
96 | */
97 | public $auto_increment;
98 |
99 | /**
100 | * Name of the sequence to use for this column if any.
101 | * @var boolean
102 | */
103 | public $sequence;
104 |
105 | /**
106 | * Cast a value to an integer type safely
107 | *
108 | * This will attempt to cast a value to an integer,
109 | * unless its detected that the casting will cause
110 | * the number to overflow or lose precision, in which
111 | * case the number will be returned as a string, so
112 | * that large integers (BIGINTS, unsigned INTS, etc)
113 | * can still be stored without error
114 | *
115 | * This would ideally be done with bcmath or gmp, but
116 | * requiring a new PHP extension for a bug-fix is a
117 | * little ridiculous
118 | *
119 | * @param mixed $value The value to cast
120 | * @return int|string type-casted value
121 | */
122 | public static function castIntegerSafely($value)
123 | {
124 | if (is_int($value))
125 | return $value;
126 |
127 | // Its just a decimal number
128 | elseif (is_numeric($value) && floor($value) != $value)
129 | return (int) $value;
130 |
131 | // If adding 0 to a string causes a float conversion,
132 | // we have a number over PHP_INT_MAX
133 | elseif (is_string($value) && is_float($value + 0))
134 | return (string) $value;
135 |
136 | // If a float was passed and its greater than PHP_INT_MAX
137 | // (which could be wrong due to floating point precision)
138 | // We'll also check for equal to (>=) in case the precision
139 | // loss creates an overflow on casting
140 | elseif (is_float($value) && $value >= PHP_INT_MAX)
141 | return number_format($value, 0, '', '');
142 |
143 | return (int) $value;
144 | }
145 |
146 | /**
147 | * Casts a value to the column's type.
148 | *
149 | * @param mixed $value The value to cast
150 | * @param Connection $connection The Connection this column belongs to
151 | * @return mixed type-casted value
152 | */
153 | public function cast($value, $connection)
154 | {
155 | if ($value === null)
156 | return null;
157 |
158 | switch ($this->type)
159 | {
160 | case self::STRING: return (string)$value;
161 | case self::INTEGER: return static::castIntegerSafely($value);
162 | case self::DECIMAL: return (double)$value;
163 | case self::DATETIME:
164 | case self::DATE:
165 | if (!$value)
166 | return null;
167 |
168 | $date_class = Config::instance()->get_date_class();
169 |
170 | if ($value instanceof $date_class)
171 | return $value;
172 |
173 | if ($value instanceof \DateTime)
174 | return $date_class::createFromFormat(
175 | Connection::DATETIME_TRANSLATE_FORMAT,
176 | $value->format(Connection::DATETIME_TRANSLATE_FORMAT),
177 | $value->getTimezone()
178 | );
179 |
180 | return $connection->string_to_datetime($value);
181 | }
182 | return $value;
183 | }
184 |
185 | /**
186 | * Sets the $type member variable.
187 | * @return mixed
188 | */
189 | public function map_raw_type()
190 | {
191 | if ($this->raw_type == 'integer')
192 | $this->raw_type = 'int';
193 |
194 | if (array_key_exists($this->raw_type,self::$TYPE_MAPPING))
195 | $this->type = self::$TYPE_MAPPING[$this->raw_type];
196 | else
197 | $this->type = self::STRING;
198 |
199 | return $this->type;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/lib/Config.php:
--------------------------------------------------------------------------------
1 |
12 | * ActiveRecord::initialize(function($cfg) {
13 | * $cfg->set_model_home('models');
14 | * $cfg->set_connections(array(
15 | * 'development' => 'mysql://user:pass@development.com/awesome_development',
16 | * 'production' => 'mysql://user:pass@production.com/awesome_production'));
17 | * });
18 | *
19 | *
20 | * @package ActiveRecord
21 | */
22 | class Config extends Singleton
23 | {
24 | /**
25 | * Name of the connection to use by default.
26 | *
27 | *
28 | * ActiveRecord\Config::initialize(function($cfg) {
29 | * $cfg->set_model_directory('/your/app/models');
30 | * $cfg->set_connections(array(
31 | * 'development' => 'mysql://user:pass@development.com/awesome_development',
32 | * 'production' => 'mysql://user:pass@production.com/awesome_production'));
33 | * });
34 | *
35 | *
36 | * This is a singleton class so you can retrieve the {@link Singleton} instance by doing:
37 | *
38 | *
39 | * $config = ActiveRecord\Config::instance();
40 | *
41 | *
42 | * @var string
43 | */
44 | private $default_connection = 'development';
45 |
46 | /**
47 | * Contains the list of database connection strings.
48 | *
49 | * @var array
50 | */
51 | private $connections = array();
52 |
53 | /**
54 | * Directory for the auto_loading of model classes.
55 | *
56 | * @see activerecord_autoload
57 | * @var string
58 | */
59 | private $model_directory;
60 |
61 | /**
62 | * Switch for logging.
63 | *
64 | * @var bool
65 | */
66 | private $logging = false;
67 |
68 | /**
69 | * Contains a Logger object that must impelement a log() method.
70 | *
71 | * @var object
72 | */
73 | private $logger;
74 |
75 | /**
76 | * Contains the class name for the Date class to use. Must have a public format() method and a
77 | * public static createFromFormat($format, $time) method
78 | *
79 | * @var string
80 | */
81 | private $date_class = 'ActiveRecord\\DateTime';
82 |
83 | /**
84 | * The format to serialize DateTime values into.
85 | *
86 | * @var string
87 | */
88 | private $date_format = \DateTime::ISO8601;
89 |
90 | /**
91 | * Allows config initialization using a closure.
92 | *
93 | * This method is just syntatic sugar.
94 | *
95 | *
96 | * ActiveRecord\Config::initialize(function($cfg) {
97 | * $cfg->set_model_directory('/path/to/your/model_directory');
98 | * $cfg->set_connections(array(
99 | * 'development' => 'mysql://username:password@127.0.0.1/database_name'));
100 | * });
101 | *
102 | *
103 | * You can also initialize by grabbing the singleton object:
104 | *
105 | *
106 | * $cfg = ActiveRecord\Config::instance();
107 | * $cfg->set_model_directory('/path/to/your/model_directory');
108 | * $cfg->set_connections(array('development' =>
109 | * 'mysql://username:password@localhost/database_name'));
110 | *
111 | *
112 | * @param Closure $initializer A closure
113 | * @return void
114 | */
115 | public static function initialize(Closure $initializer)
116 | {
117 | $initializer(parent::instance());
118 | }
119 |
120 | /**
121 | * Sets the list of database connection strings.
122 | *
123 | *
124 | * $config->set_connections(array(
125 | * 'development' => 'mysql://username:password@127.0.0.1/database_name'));
126 | *
127 | *
128 | * @param array $connections Array of connections
129 | * @param string $default_connection Optionally specify the default_connection
130 | * @return void
131 | * @throws ActiveRecord\ConfigException
132 | */
133 | public function set_connections($connections, $default_connection=null)
134 | {
135 | if (!is_array($connections))
136 | throw new ConfigException("Connections must be an array");
137 |
138 | if ($default_connection)
139 | $this->set_default_connection($default_connection);
140 |
141 | $this->connections = $connections;
142 | }
143 |
144 | /**
145 | * Returns the connection strings array.
146 | *
147 | * @return array
148 | */
149 | public function get_connections()
150 | {
151 | return $this->connections;
152 | }
153 |
154 | /**
155 | * Returns a connection string if found otherwise null.
156 | *
157 | * @param string $name Name of connection to retrieve
158 | * @return string connection info for specified connection name
159 | */
160 | public function get_connection($name)
161 | {
162 | if (array_key_exists($name, $this->connections))
163 | return $this->connections[$name];
164 |
165 | return null;
166 | }
167 |
168 | /**
169 | * Returns the default connection string or null if there is none.
170 | *
171 | * @return string
172 | */
173 | public function get_default_connection_string()
174 | {
175 | return array_key_exists($this->default_connection,$this->connections) ?
176 | $this->connections[$this->default_connection] : null;
177 | }
178 |
179 | /**
180 | * Returns the name of the default connection.
181 | *
182 | * @return string
183 | */
184 | public function get_default_connection()
185 | {
186 | return $this->default_connection;
187 | }
188 |
189 | /**
190 | * Set the name of the default connection.
191 | *
192 | * @param string $name Name of a connection in the connections array
193 | * @return void
194 | */
195 | public function set_default_connection($name)
196 | {
197 | $this->default_connection = $name;
198 | }
199 |
200 | /**
201 | * Sets the directory where models are located.
202 | *
203 | * @param string $dir Directory path containing your models
204 | * @return void
205 | */
206 | public function set_model_directory($dir)
207 | {
208 | $this->model_directory = $dir;
209 | }
210 |
211 | /**
212 | * Returns the model directory.
213 | *
214 | * @return string
215 | * @throws ConfigException if specified directory was not found
216 | */
217 | public function get_model_directory()
218 | {
219 | if ($this->model_directory && !file_exists($this->model_directory))
220 | throw new ConfigException('Invalid or non-existent directory: '.$this->model_directory);
221 |
222 | return $this->model_directory;
223 | }
224 |
225 | /**
226 | * Turn on/off logging
227 | *
228 | * @param boolean $bool
229 | * @return void
230 | */
231 | public function set_logging($bool)
232 | {
233 | $this->logging = (bool)$bool;
234 | }
235 |
236 | /**
237 | * Sets the logger object for future SQL logging
238 | *
239 | * @param object $logger
240 | * @return void
241 | * @throws ConfigException if Logger objecct does not implement public log()
242 | */
243 | public function set_logger($logger)
244 | {
245 | $klass = Reflections::instance()->add($logger)->get($logger);
246 |
247 | if (!$klass->getMethod('log') || !$klass->getMethod('log')->isPublic())
248 | throw new ConfigException("Logger object must implement a public log method");
249 |
250 | $this->logger = $logger;
251 | }
252 |
253 | /**
254 | * Return whether or not logging is on
255 | *
256 | * @return boolean
257 | */
258 | public function get_logging()
259 | {
260 | return $this->logging;
261 | }
262 |
263 | /**
264 | * Returns the logger
265 | *
266 | * @return object
267 | */
268 | public function get_logger()
269 | {
270 | return $this->logger;
271 | }
272 |
273 | public function set_date_class($date_class)
274 | {
275 | try {
276 | $klass = Reflections::instance()->add($date_class)->get($date_class);
277 | } catch (\ReflectionException $e) {
278 | throw new ConfigException("Cannot find date class");
279 | }
280 |
281 | if (!$klass->hasMethod('format') || !$klass->getMethod('format')->isPublic())
282 | throw new ConfigException('Given date class must have a "public format($format = null)" method');
283 |
284 | if (!$klass->hasMethod('createFromFormat') || !$klass->getMethod('createFromFormat')->isPublic())
285 | throw new ConfigException('Given date class must have a "public static createFromFormat($format, $time)" method');
286 |
287 | $this->date_class = $date_class;
288 | }
289 |
290 | public function get_date_class()
291 | {
292 | return $this->date_class;
293 | }
294 |
295 | /**
296 | * @deprecated
297 | */
298 | public function get_date_format()
299 | {
300 | trigger_error('Use ActiveRecord\Serialization::$DATETIME_FORMAT. Config::get_date_format() has been deprecated.', E_USER_DEPRECATED);
301 | return Serialization::$DATETIME_FORMAT;
302 | }
303 |
304 | /**
305 | * @deprecated
306 | */
307 | public function set_date_format($format)
308 | {
309 | trigger_error('Use ActiveRecord\Serialization::$DATETIME_FORMAT. Config::set_date_format() has been deprecated.', E_USER_DEPRECATED);
310 | Serialization::$DATETIME_FORMAT = $format;
311 | }
312 |
313 | /**
314 | * Sets the url for the cache server to enable query caching.
315 | *
316 | * Only table schema queries are cached at the moment. A general query cache
317 | * will follow.
318 | *
319 | * Example:
320 | *
321 | *
322 | * $config->set_cache("memcached://localhost");
323 | * $config->set_cache("memcached://localhost",array("expire" => 60));
324 | *
325 | *
326 | * @param string $url Url to your cache server.
327 | * @param array $options Array of options
328 | */
329 | public function set_cache($url, $options=array())
330 | {
331 | Cache::initialize($url,$options);
332 | }
333 | }
--------------------------------------------------------------------------------
/lib/ConnectionManager.php:
--------------------------------------------------------------------------------
1 | get_default_connection();
31 |
32 | if (!isset(self::$connections[$name]) || !self::$connections[$name]->connection)
33 | self::$connections[$name] = Connection::instance($config->get_connection($name));
34 |
35 | return self::$connections[$name];
36 | }
37 |
38 | /**
39 | * Drops the connection from the connection manager. Does not actually close it since there
40 | * is no close method in PDO.
41 | *
42 | * If $name is null then the default connection will be returned.
43 | *
44 | * @param string $name Name of the connection to forget about
45 | */
46 | public static function drop_connection($name=null)
47 | {
48 | $config = Config::instance();
49 | $name = $name ? $name : $config->get_default_connection();
50 |
51 | if (isset(self::$connections[$name]))
52 | unset(self::$connections[$name]);
53 | }
54 | }
--------------------------------------------------------------------------------
/lib/DateTime.php:
--------------------------------------------------------------------------------
1 |
15 | * $now = new ActiveRecord\DateTime('2010-01-02 03:04:05');
16 | * ActiveRecord\DateTime::$DEFAULT_FORMAT = 'short';
17 | *
18 | * echo $now->format(); # 02 Jan 03:04
19 | * echo $now->format('atom'); # 2010-01-02T03:04:05-05:00
20 | * echo $now->format('Y-m-d'); # 2010-01-02
21 | *
22 | * # __toString() uses the default formatter
23 | * echo (string)$now; # 02 Jan 03:04
24 | *
25 | *
26 | * You can also add your own pre-defined friendly formatters:
27 | *
28 | *
29 | * ActiveRecord\DateTime::$FORMATS['awesome_format'] = 'H:i:s m/d/Y';
30 | * echo $now->format('awesome_format') # 03:04:05 01/02/2010
31 | *
32 | *
33 | * @package ActiveRecord
34 | * @see http://php.net/manual/en/class.datetime.php
35 | */
36 | class DateTime extends \DateTime implements DateTimeInterface
37 | {
38 | /**
39 | * Default format used for format() and __toString()
40 | */
41 | public static $DEFAULT_FORMAT = 'rfc2822';
42 |
43 | /**
44 | * Pre-defined format strings.
45 | */
46 | public static $FORMATS = array(
47 | 'db' => 'Y-m-d H:i:s',
48 | 'number' => 'YmdHis',
49 | 'time' => 'H:i',
50 | 'short' => 'd M H:i',
51 | 'long' => 'F d, Y H:i',
52 | 'atom' => \DateTime::ATOM,
53 | 'cookie' => \DateTime::COOKIE,
54 | 'iso8601' => \DateTime::ISO8601,
55 | 'rfc822' => \DateTime::RFC822,
56 | 'rfc850' => \DateTime::RFC850,
57 | 'rfc1036' => \DateTime::RFC1036,
58 | 'rfc1123' => \DateTime::RFC1123,
59 | 'rfc2822' => \DateTime::RFC2822,
60 | 'rfc3339' => \DateTime::RFC3339,
61 | 'rss' => \DateTime::RSS,
62 | 'w3c' => \DateTime::W3C);
63 |
64 | private $model;
65 | private $attribute_name;
66 |
67 | public function attribute_of($model, $attribute_name)
68 | {
69 | $this->model = $model;
70 | $this->attribute_name = $attribute_name;
71 | }
72 |
73 | /**
74 | * Formats the DateTime to the specified format.
75 | *
76 | *
77 | * $datetime->format(); # uses the format defined in DateTime::$DEFAULT_FORMAT
78 | * $datetime->format('short'); # d M H:i
79 | * $datetime->format('Y-m-d'); # Y-m-d
80 | *
81 | *
82 | * @see FORMATS
83 | * @see get_format
84 | * @param string $format A format string accepted by get_format()
85 | * @return string formatted date and time string
86 | */
87 | public function format($format=null)
88 | {
89 | return parent::format(self::get_format($format));
90 | }
91 |
92 | /**
93 | * Returns the format string.
94 | *
95 | * If $format is a pre-defined format in $FORMATS it will return that otherwise
96 | * it will assume $format is a format string itself.
97 | *
98 | * @see FORMATS
99 | * @param string $format A pre-defined string format or a raw format string
100 | * @return string a format string
101 | */
102 | public static function get_format($format=null)
103 | {
104 | // use default format if no format specified
105 | if (!$format)
106 | $format = self::$DEFAULT_FORMAT;
107 |
108 | // format is a friendly
109 | if (array_key_exists($format, self::$FORMATS))
110 | return self::$FORMATS[$format];
111 |
112 | // raw format
113 | return $format;
114 | }
115 |
116 | /**
117 | * This needs to be overriden so it returns an instance of this class instead of PHP's \DateTime.
118 | * See http://php.net/manual/en/datetime.createfromformat.php
119 | */
120 | public static function createFromFormat($format, $time, $tz = null)
121 | {
122 | $phpDate = $tz ? parent::createFromFormat($format, $time, $tz) : parent::createFromFormat($format, $time);
123 | if (!$phpDate)
124 | return false;
125 | // convert to this class using the timestamp
126 | $ourDate = new static(null, $phpDate->getTimezone());
127 | $ourDate->setTimestamp($phpDate->getTimestamp());
128 | return $ourDate;
129 | }
130 |
131 | public function __toString()
132 | {
133 | return $this->format();
134 | }
135 |
136 | /**
137 | * Handle PHP object `clone`.
138 | *
139 | * This makes sure that the object doesn't still flag an attached model as
140 | * dirty after cloning the DateTime object and making modifications to it.
141 | *
142 | * @return void
143 | */
144 | public function __clone()
145 | {
146 | $this->model = null;
147 | $this->attribute_name = null;
148 | }
149 |
150 | private function flag_dirty()
151 | {
152 | if ($this->model)
153 | $this->model->flag_dirty($this->attribute_name);
154 | }
155 |
156 | public function setDate($year, $month, $day)
157 | {
158 | $this->flag_dirty();
159 | return parent::setDate($year, $month, $day);
160 | }
161 |
162 | public function setISODate($year, $week , $day = 1)
163 | {
164 | $this->flag_dirty();
165 | return parent::setISODate($year, $week, $day);
166 | }
167 |
168 | public function setTime($hour, $minute, $second = 0, $microseconds = 0)
169 | {
170 | $this->flag_dirty();
171 | return parent::setTime($hour, $minute, $second);
172 | }
173 |
174 | public function setTimestamp($unixtimestamp)
175 | {
176 | $this->flag_dirty();
177 | return parent::setTimestamp($unixtimestamp);
178 | }
179 |
180 | public function setTimezone($timezone)
181 | {
182 | $this->flag_dirty();
183 | return parent::setTimezone($timezone);
184 | }
185 |
186 | public function modify($modify)
187 | {
188 | $this->flag_dirty();
189 | return parent::modify($modify);
190 | }
191 |
192 | public function add($interval)
193 | {
194 | $this->flag_dirty();
195 | return parent::add($interval);
196 | }
197 |
198 | public function sub($interval)
199 | {
200 | $this->flag_dirty();
201 | return parent::sub($interval);
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/lib/DateTimeInterface.php:
--------------------------------------------------------------------------------
1 | assign_attribute() will
9 | * know to call attribute_of() on passed values. This is so the DateTime object can flag the model
10 | * as dirty via $model->flag_dirty() when one of its setters is called.
11 | *
12 | * @package ActiveRecord
13 | * @see http://php.net/manual/en/class.datetime.php
14 | */
15 | interface DateTimeInterface
16 | {
17 | /**
18 | * Indicates this object is an attribute of the specified model, with the given attribute name.
19 | *
20 | * @param Model $model The model this object is an attribute of
21 | * @param string $attribute_name The attribute name
22 | * @return void
23 | */
24 | public function attribute_of($model, $attribute_name);
25 |
26 | /**
27 | * Formats the DateTime to the specified format.
28 | */
29 | public function format($format=null);
30 |
31 | /**
32 | * See http://php.net/manual/en/datetime.createfromformat.php
33 | */
34 | public static function createFromFormat($format, $time, $tz = null);
35 | }
36 |
--------------------------------------------------------------------------------
/lib/Exceptions.php:
--------------------------------------------------------------------------------
1 | connection->errorInfo()),
36 | intval($adapter_or_string_or_mystery->connection->errorCode()));
37 | }
38 | elseif ($adapter_or_string_or_mystery instanceof \PDOStatement)
39 | {
40 | parent::__construct(
41 | join(", ",$adapter_or_string_or_mystery->errorInfo()),
42 | intval($adapter_or_string_or_mystery->errorCode()));
43 | }
44 | else
45 | parent::__construct($adapter_or_string_or_mystery);
46 | }
47 | }
48 |
49 | /**
50 | * Thrown by {@link Model}.
51 | *
52 | * @package ActiveRecord
53 | */
54 | class ModelException extends ActiveRecordException {}
55 |
56 | /**
57 | * Thrown by {@link Expressions}.
58 | *
59 | * @package ActiveRecord
60 | */
61 | class ExpressionsException extends ActiveRecordException {}
62 |
63 | /**
64 | * Thrown for configuration problems.
65 | *
66 | * @package ActiveRecord
67 | */
68 | class ConfigException extends ActiveRecordException {}
69 |
70 | /**
71 | * Thrown for cache problems.
72 | *
73 | * @package ActiveRecord
74 | */
75 | class CacheException extends ActiveRecordException {}
76 |
77 | /**
78 | * Thrown when attempting to access an invalid property on a {@link Model}.
79 | *
80 | * @package ActiveRecord
81 | */
82 | class UndefinedPropertyException extends ModelException
83 | {
84 | /**
85 | * Sets the exception message to show the undefined property's name.
86 | *
87 | * @param str $property_name name of undefined property
88 | * @return void
89 | */
90 | public function __construct($class_name, $property_name)
91 | {
92 | if (is_array($property_name))
93 | {
94 | $this->message = implode("\r\n", $property_name);
95 | return;
96 | }
97 |
98 | $this->message = "Undefined property: {$class_name}->{$property_name} in {$this->file} on line {$this->line}";
99 | parent::__construct();
100 | }
101 | }
102 |
103 | /**
104 | * Thrown when attempting to perform a write operation on a {@link Model} that is in read-only mode.
105 | *
106 | * @package ActiveRecord
107 | */
108 | class ReadOnlyException extends ModelException
109 | {
110 | /**
111 | * Sets the exception message to show the undefined property's name.
112 | *
113 | * @param str $class_name name of the model that is read only
114 | * @param str $method_name name of method which attempted to modify the model
115 | * @return void
116 | */
117 | public function __construct($class_name, $method_name)
118 | {
119 | $this->message = "{$class_name}::{$method_name}() cannot be invoked because this model is set to read only";
120 | parent::__construct();
121 | }
122 | }
123 |
124 | /**
125 | * Thrown for validations exceptions.
126 | *
127 | * @package ActiveRecord
128 | */
129 | class ValidationsArgumentError extends ActiveRecordException {}
130 |
131 | /**
132 | * Thrown for relationship exceptions.
133 | *
134 | * @package ActiveRecord
135 | */
136 | class RelationshipException extends ActiveRecordException {}
137 |
138 | /**
139 | * Thrown for has many thru exceptions.
140 | *
141 | * @package ActiveRecord
142 | */
143 | class HasManyThroughAssociationException extends RelationshipException {}
144 |
--------------------------------------------------------------------------------
/lib/Expressions.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
29 |
30 | if (is_array($expressions))
31 | {
32 | $glue = func_num_args() > 2 ? func_get_arg(2) : ' AND ';
33 | list($expressions,$values) = $this->build_sql_from_hash($expressions,$glue);
34 | }
35 |
36 | if ($expressions != '')
37 | {
38 | if (!$values)
39 | $values = array_slice(func_get_args(),2);
40 |
41 | $this->values = $values;
42 | $this->expressions = $expressions;
43 | }
44 | }
45 |
46 | /**
47 | * Bind a value to the specific one based index. There must be a bind marker
48 | * for each value bound or to_s() will throw an exception.
49 | */
50 | public function bind($parameter_number, $value)
51 | {
52 | if ($parameter_number <= 0)
53 | throw new ExpressionsException("Invalid parameter index: $parameter_number");
54 |
55 | $this->values[$parameter_number-1] = $value;
56 | }
57 |
58 | public function bind_values($values)
59 | {
60 | $this->values = $values;
61 | }
62 |
63 | /**
64 | * Returns all the values currently bound.
65 | */
66 | public function values()
67 | {
68 | return $this->values;
69 | }
70 |
71 | /**
72 | * Returns the connection object.
73 | */
74 | public function get_connection()
75 | {
76 | return $this->connection;
77 | }
78 |
79 | /**
80 | * Sets the connection object. It is highly recommended to set this so we can
81 | * use the adapter's native escaping mechanism.
82 | *
83 | * @param string $connection a Connection instance
84 | */
85 | public function set_connection($connection)
86 | {
87 | $this->connection = $connection;
88 | }
89 |
90 | public function to_s($substitute=false, &$options=null)
91 | {
92 | if (!$options) $options = array();
93 |
94 | $values = array_key_exists('values',$options) ? $options['values'] : $this->values;
95 |
96 | $ret = "";
97 | $replace = array();
98 | $num_values = count($values);
99 | $len = strlen($this->expressions);
100 | $quotes = 0;
101 |
102 | for ($i=0,$n=strlen($this->expressions),$j=0; $i<$n; ++$i)
103 | {
104 | $ch = $this->expressions[$i];
105 |
106 | if ($ch == self::ParameterMarker)
107 | {
108 | if ($quotes % 2 == 0)
109 | {
110 | if ($j > $num_values-1)
111 | throw new ExpressionsException("No bound parameter for index $j");
112 |
113 | $ch = $this->substitute($values,$substitute,$i,$j++);
114 | }
115 | }
116 | elseif ($ch == '\'' && $i > 0 && $this->expressions[$i-1] != '\\')
117 | ++$quotes;
118 |
119 | $ret .= $ch;
120 | }
121 | return $ret;
122 | }
123 |
124 | private function build_sql_from_hash(&$hash, $glue)
125 | {
126 | $sql = $g = "";
127 |
128 | foreach ($hash as $name => $value)
129 | {
130 | if ($this->connection)
131 | $name = $this->connection->quote_name($name);
132 |
133 | if (is_array($value))
134 | $sql .= "$g$name IN(?)";
135 | elseif (is_null($value))
136 | $sql .= "$g$name IS ?";
137 | else
138 | $sql .= "$g$name=?";
139 |
140 | $g = $glue;
141 | }
142 | return array($sql,array_values($hash));
143 | }
144 |
145 | private function substitute(&$values, $substitute, $pos, $parameter_index)
146 | {
147 | $value = $values[$parameter_index];
148 |
149 | if (is_array($value))
150 | {
151 | $value_count = count($value);
152 |
153 | if ($value_count === 0)
154 | if ($substitute)
155 | return 'NULL';
156 | else
157 | return self::ParameterMarker;
158 |
159 | if ($substitute)
160 | {
161 | $ret = '';
162 |
163 | for ($i=0, $n=$value_count; $i<$n; ++$i)
164 | $ret .= ($i > 0 ? ',' : '') . $this->stringify_value($value[$i]);
165 |
166 | return $ret;
167 | }
168 | return join(',',array_fill(0,$value_count,self::ParameterMarker));
169 | }
170 |
171 | if ($substitute)
172 | return $this->stringify_value($value);
173 |
174 | return $this->expressions[$pos];
175 | }
176 |
177 | private function stringify_value($value)
178 | {
179 | if (is_null($value))
180 | return "NULL";
181 |
182 | return is_string($value) ? $this->quote_string($value) : $value;
183 | }
184 |
185 | private function quote_string($value)
186 | {
187 | if ($this->connection)
188 | return $this->connection->escape($value);
189 |
190 | return "'" . str_replace("'","''",$value) . "'";
191 | }
192 | }
--------------------------------------------------------------------------------
/lib/Inflector.php:
--------------------------------------------------------------------------------
1 | 0)
46 | $camelized[0] = strtolower($camelized[0]);
47 |
48 | return $camelized;
49 | }
50 |
51 | /**
52 | * Determines if a string contains all uppercase characters.
53 | *
54 | * @param string $s string to check
55 | * @return bool
56 | */
57 | public static function is_upper($s)
58 | {
59 | return (strtoupper($s) === $s);
60 | }
61 |
62 | /**
63 | * Determines if a string contains all lowercase characters.
64 | *
65 | * @param string $s string to check
66 | * @return bool
67 | */
68 | public static function is_lower($s)
69 | {
70 | return (strtolower($s) === $s);
71 | }
72 |
73 | /**
74 | * Convert a camelized string to a lowercase, underscored string.
75 | *
76 | * @param string $s string to convert
77 | * @return string
78 | */
79 | public function uncamelize($s)
80 | {
81 | $normalized = '';
82 |
83 | for ($i=0,$n=strlen($s); $i<$n; ++$i)
84 | {
85 | if (ctype_alpha($s[$i]) && self::is_upper($s[$i]))
86 | $normalized .= '_' . strtolower($s[$i]);
87 | else
88 | $normalized .= $s[$i];
89 | }
90 | return trim($normalized,' _');
91 | }
92 |
93 | /**
94 | * Convert a string with space into a underscored equivalent.
95 | *
96 | * @param string $s string to convert
97 | * @return string
98 | */
99 | public function underscorify($s)
100 | {
101 | return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'),array('_','\\1_\\2'),trim($s));
102 | }
103 |
104 | public function keyify($class_name)
105 | {
106 | return strtolower($this->underscorify(denamespace($class_name))) . '_id';
107 | }
108 |
109 | abstract function variablize($s);
110 | }
111 |
112 | /**
113 | * @package ActiveRecord
114 | */
115 | class StandardInflector extends Inflector
116 | {
117 | public function tableize($s) { return Utils::pluralize(strtolower($this->underscorify($s))); }
118 | public function variablize($s) { return str_replace(array('-',' '),array('_','_'),strtolower(trim($s))); }
119 | }
--------------------------------------------------------------------------------
/lib/Reflections.php:
--------------------------------------------------------------------------------
1 | add('class')->get()
27 | */
28 | public function add($class=null)
29 | {
30 | $class = $this->get_class($class);
31 |
32 | if (!isset($this->reflections[$class]))
33 | $this->reflections[$class] = new ReflectionClass($class);
34 |
35 | return $this;
36 | }
37 |
38 | /**
39 | * Destroys the cached ReflectionClass.
40 | *
41 | * Put this here mainly for testing purposes.
42 | *
43 | * @param string $class Name of a class.
44 | * @return void
45 | */
46 | public function destroy($class)
47 | {
48 | if (isset($this->reflections[$class]))
49 | $this->reflections[$class] = null;
50 | }
51 |
52 | /**
53 | * Get a cached ReflectionClass.
54 | *
55 | * @param string $class Optional name of a class
56 | * @return mixed null or a ReflectionClass instance
57 | * @throws ActiveRecordException if class was not found
58 | */
59 | public function get($class=null)
60 | {
61 | $class = $this->get_class($class);
62 |
63 | if (isset($this->reflections[$class]))
64 | return $this->reflections[$class];
65 |
66 | throw new ActiveRecordException("Class not found: $class");
67 | }
68 |
69 | /**
70 | * Retrieve a class name to be reflected.
71 | *
72 | * @param mixed $mixed An object or name of a class
73 | * @return string
74 | */
75 | private function get_class($mixed=null)
76 | {
77 | if (is_object($mixed))
78 | return get_class($mixed);
79 |
80 | if (!is_null($mixed))
81 | return $mixed;
82 |
83 | return $this->get_called_class();
84 | }
85 | }
--------------------------------------------------------------------------------
/lib/Singleton.php:
--------------------------------------------------------------------------------
1 | query("SHOW COLUMNS FROM $table");
26 | }
27 |
28 | public function query_for_tables()
29 | {
30 | return $this->query('SHOW TABLES');
31 | }
32 |
33 | public function create_column(&$column)
34 | {
35 | $c = new Column();
36 | $c->inflected_name = Inflector::instance()->variablize($column['field']);
37 | $c->name = $column['field'];
38 | $c->nullable = ($column['null'] === 'YES' ? true : false);
39 | $c->pk = ($column['key'] === 'PRI' ? true : false);
40 | $c->auto_increment = ($column['extra'] === 'auto_increment' ? true : false);
41 |
42 | if ($column['type'] == 'timestamp' || $column['type'] == 'datetime')
43 | {
44 | $c->raw_type = 'datetime';
45 | $c->length = 19;
46 | }
47 | elseif ($column['type'] == 'date')
48 | {
49 | $c->raw_type = 'date';
50 | $c->length = 10;
51 | }
52 | elseif ($column['type'] == 'time')
53 | {
54 | $c->raw_type = 'time';
55 | $c->length = 8;
56 | }
57 | else
58 | {
59 | preg_match('/^([A-Za-z0-9_]+)(\(([0-9]+(,[0-9]+)?)\))?/',$column['type'],$matches);
60 |
61 | $c->raw_type = (count($matches) > 0 ? $matches[1] : $column['type']);
62 |
63 | if (count($matches) >= 4)
64 | $c->length = intval($matches[3]);
65 | }
66 |
67 | $c->map_raw_type();
68 | $c->default = $c->cast($column['default'],$this);
69 |
70 | return $c;
71 | }
72 |
73 | public function set_encoding($charset)
74 | {
75 | $params = array($charset);
76 | $this->query('SET NAMES ?',$params);
77 | }
78 |
79 | public function accepts_limit_and_order_for_update_and_delete() { return true; }
80 |
81 | public function native_database_types()
82 | {
83 | return array(
84 | 'primary_key' => 'int(11) UNSIGNED DEFAULT NULL auto_increment PRIMARY KEY',
85 | 'string' => array('name' => 'varchar', 'length' => 255),
86 | 'text' => array('name' => 'text'),
87 | 'integer' => array('name' => 'int', 'length' => 11),
88 | 'float' => array('name' => 'float'),
89 | 'datetime' => array('name' => 'datetime'),
90 | 'timestamp' => array('name' => 'datetime'),
91 | 'time' => array('name' => 'time'),
92 | 'date' => array('name' => 'date'),
93 | 'binary' => array('name' => 'blob'),
94 | 'boolean' => array('name' => 'tinyint', 'length' => 1)
95 | );
96 | }
97 |
98 | }
99 | ?>
100 |
--------------------------------------------------------------------------------
/lib/adapters/OciAdapter.php:
--------------------------------------------------------------------------------
1 | dsn_params = isset($info->charset) ? ";charset=$info->charset" : "";
25 | $this->connection = new PDO("oci:dbname=//$info->host/$info->db$this->dsn_params",$info->user,$info->pass,static::$PDO_OPTIONS);
26 | } catch (PDOException $e) {
27 | throw new DatabaseException($e);
28 | }
29 | }
30 |
31 | public function supports_sequences() { return true; }
32 |
33 | public function get_next_sequence_value($sequence_name)
34 | {
35 | return $this->query_and_fetch_one('SELECT ' . $this->next_sequence_value($sequence_name) . ' FROM dual');
36 | }
37 |
38 | public function next_sequence_value($sequence_name)
39 | {
40 | return "$sequence_name.nextval";
41 | }
42 |
43 | public function date_to_string($datetime)
44 | {
45 | return $datetime->format('d-M-Y');
46 | }
47 |
48 | public function datetime_to_string($datetime)
49 | {
50 | return $datetime->format('d-M-Y h:i:s A');
51 | }
52 |
53 | // $string = DD-MON-YYYY HH12:MI:SS(\.[0-9]+) AM
54 | public function string_to_datetime($string)
55 | {
56 | return parent::string_to_datetime(str_replace('.000000','',$string));
57 | }
58 |
59 | public function limit($sql, $offset, $limit)
60 | {
61 | $offset = intval($offset);
62 | $stop = $offset + intval($limit);
63 | return
64 | "SELECT * FROM (SELECT a.*, rownum ar_rnum__ FROM ($sql) a " .
65 | "WHERE rownum <= $stop) WHERE ar_rnum__ > $offset";
66 | }
67 |
68 | public function query_column_info($table)
69 | {
70 | $sql =
71 | "SELECT c.column_name, c.data_type, c.data_length, c.data_scale, c.data_default, c.nullable, " .
72 | "(SELECT a.constraint_type " .
73 | "FROM all_constraints a, all_cons_columns b " .
74 | "WHERE a.constraint_type='P' " .
75 | "AND a.constraint_name=b.constraint_name " .
76 | "AND a.table_name = t.table_name AND b.column_name=c.column_name) AS pk " .
77 | "FROM user_tables t " .
78 | "INNER JOIN user_tab_columns c on(t.table_name=c.table_name) " .
79 | "WHERE t.table_name=?";
80 |
81 | $values = array(strtoupper($table));
82 | return $this->query($sql,$values);
83 | }
84 |
85 | public function query_for_tables()
86 | {
87 | return $this->query("SELECT table_name FROM user_tables");
88 | }
89 |
90 | public function create_column(&$column)
91 | {
92 | $column['column_name'] = strtolower($column['column_name']);
93 | $column['data_type'] = strtolower(preg_replace('/\(.*?\)/','',$column['data_type']));
94 |
95 | if ($column['data_default'] !== null)
96 | $column['data_default'] = trim($column['data_default'],"' ");
97 |
98 | if ($column['data_type'] == 'number')
99 | {
100 | if ($column['data_scale'] > 0)
101 | $column['data_type'] = 'decimal';
102 | elseif ($column['data_scale'] == 0)
103 | $column['data_type'] = 'int';
104 | }
105 |
106 | $c = new Column();
107 | $c->inflected_name = Inflector::instance()->variablize($column['column_name']);
108 | $c->name = $column['column_name'];
109 | $c->nullable = $column['nullable'] == 'Y' ? true : false;
110 | $c->pk = $column['pk'] == 'P' ? true : false;
111 | $c->length = $column['data_length'];
112 |
113 | if ($column['data_type'] == 'timestamp')
114 | $c->raw_type = 'datetime';
115 | else
116 | $c->raw_type = $column['data_type'];
117 |
118 | $c->map_raw_type();
119 | $c->default = $c->cast($column['data_default'],$this);
120 |
121 | return $c;
122 | }
123 |
124 | public function set_encoding($charset)
125 | {
126 | // is handled in the constructor
127 | }
128 |
129 | public function native_database_types()
130 | {
131 | return array(
132 | 'primary_key' => "NUMBER(38) NOT NULL PRIMARY KEY",
133 | 'string' => array('name' => 'VARCHAR2', 'length' => 255),
134 | 'text' => array('name' => 'CLOB'),
135 | 'integer' => array('name' => 'NUMBER', 'length' => 38),
136 | 'float' => array('name' => 'NUMBER'),
137 | 'datetime' => array('name' => 'DATE'),
138 | 'timestamp' => array('name' => 'DATE'),
139 | 'time' => array('name' => 'DATE'),
140 | 'date' => array('name' => 'DATE'),
141 | 'binary' => array('name' => 'BLOB'),
142 | 'boolean' => array('name' => 'NUMBER', 'length' => 1)
143 | );
144 | }
145 | }
146 | ?>
147 |
--------------------------------------------------------------------------------
/lib/adapters/PgsqlAdapter.php:
--------------------------------------------------------------------------------
1 | 0
59 | AND a.attrelid = c.oid
60 | AND a.atttypid = t.oid
61 | ORDER BY a.attnum
62 | SQL;
63 | $values = array($table);
64 | return $this->query($sql,$values);
65 | }
66 |
67 | public function query_for_tables()
68 | {
69 | return $this->query("SELECT tablename FROM pg_tables WHERE schemaname NOT IN('information_schema','pg_catalog')");
70 | }
71 |
72 | public function create_column(&$column)
73 | {
74 | $c = new Column();
75 | $c->inflected_name = Inflector::instance()->variablize($column['field']);
76 | $c->name = $column['field'];
77 | $c->nullable = ($column['not_nullable'] ? false : true);
78 | $c->pk = ($column['pk'] ? true : false);
79 | $c->auto_increment = false;
80 |
81 | if (substr($column['type'],0,9) == 'timestamp')
82 | {
83 | $c->raw_type = 'datetime';
84 | $c->length = 19;
85 | }
86 | elseif ($column['type'] == 'date')
87 | {
88 | $c->raw_type = 'date';
89 | $c->length = 10;
90 | }
91 | else
92 | {
93 | preg_match('/^([A-Za-z0-9_]+)(\(([0-9]+(,[0-9]+)?)\))?/',$column['type'],$matches);
94 |
95 | $c->raw_type = (count($matches) > 0 ? $matches[1] : $column['type']);
96 | $c->length = count($matches) >= 4 ? intval($matches[3]) : intval($column['attlen']);
97 |
98 | if ($c->length < 0)
99 | $c->length = null;
100 | }
101 |
102 | $c->map_raw_type();
103 |
104 | if ($column['default'])
105 | {
106 | preg_match("/^nextval\('(.*)'\)$/",$column['default'],$matches);
107 |
108 | if (count($matches) == 2)
109 | $c->sequence = $matches[1];
110 | else
111 | $c->default = $c->cast($column['default'],$this);
112 | }
113 | return $c;
114 | }
115 |
116 | public function set_encoding($charset)
117 | {
118 | $this->query("SET NAMES '$charset'");
119 | }
120 |
121 | public function native_database_types()
122 | {
123 | return array(
124 | 'primary_key' => 'serial primary key',
125 | 'string' => array('name' => 'character varying', 'length' => 255),
126 | 'text' => array('name' => 'text'),
127 | 'integer' => array('name' => 'integer'),
128 | 'float' => array('name' => 'float'),
129 | 'datetime' => array('name' => 'datetime'),
130 | 'timestamp' => array('name' => 'timestamp'),
131 | 'time' => array('name' => 'time'),
132 | 'date' => array('name' => 'date'),
133 | 'binary' => array('name' => 'binary'),
134 | 'boolean' => array('name' => 'boolean')
135 | );
136 | }
137 |
138 | }
139 | ?>
140 |
--------------------------------------------------------------------------------
/lib/adapters/SqliteAdapter.php:
--------------------------------------------------------------------------------
1 | host))
22 | throw new DatabaseException("Could not find sqlite db: $info->host");
23 |
24 | $this->connection = new PDO("sqlite:$info->host",null,null,static::$PDO_OPTIONS);
25 | }
26 |
27 | public function limit($sql, $offset, $limit)
28 | {
29 | $offset = is_null($offset) ? '' : intval($offset) . ',';
30 | $limit = intval($limit);
31 | return "$sql LIMIT {$offset}$limit";
32 | }
33 |
34 | public function query_column_info($table)
35 | {
36 | return $this->query("pragma table_info($table)");
37 | }
38 |
39 | public function query_for_tables()
40 | {
41 | return $this->query("SELECT name FROM sqlite_master");
42 | }
43 |
44 | public function create_column($column)
45 | {
46 | $c = new Column();
47 | $c->inflected_name = Inflector::instance()->variablize($column['name']);
48 | $c->name = $column['name'];
49 | $c->nullable = $column['notnull'] ? false : true;
50 | $c->pk = $column['pk'] ? true : false;
51 | $c->auto_increment = in_array(
52 | strtoupper($column['type']),
53 | array('INT', 'INTEGER')
54 | ) && $c->pk;
55 |
56 | $column['type'] = preg_replace('/ +/',' ',$column['type']);
57 | $column['type'] = str_replace(array('(',')'),' ',$column['type']);
58 | $column['type'] = Utils::squeeze(' ',$column['type']);
59 | $matches = explode(' ',$column['type']);
60 |
61 | if (!empty($matches))
62 | {
63 | $c->raw_type = strtolower($matches[0]);
64 |
65 | if (count($matches) > 1)
66 | $c->length = intval($matches[1]);
67 | }
68 |
69 | $c->map_raw_type();
70 |
71 | if ($c->type == Column::DATETIME)
72 | $c->length = 19;
73 | elseif ($c->type == Column::DATE)
74 | $c->length = 10;
75 |
76 | // From SQLite3 docs: The value is a signed integer, stored in 1, 2, 3, 4, 6,
77 | // or 8 bytes depending on the magnitude of the value.
78 | // so is it ok to assume it's possible an int can always go up to 8 bytes?
79 | if ($c->type == Column::INTEGER && !$c->length)
80 | $c->length = 8;
81 |
82 | $c->default = $c->cast($column['dflt_value'],$this);
83 |
84 | return $c;
85 | }
86 |
87 | public function set_encoding($charset)
88 | {
89 | throw new ActiveRecordException("SqliteAdapter::set_charset not supported.");
90 | }
91 |
92 | public function accepts_limit_and_order_for_update_and_delete() { return true; }
93 |
94 | public function native_database_types()
95 | {
96 | return array(
97 | 'primary_key' => 'integer not null primary key',
98 | 'string' => array('name' => 'varchar', 'length' => 255),
99 | 'text' => array('name' => 'text'),
100 | 'integer' => array('name' => 'integer'),
101 | 'float' => array('name' => 'float'),
102 | 'decimal' => array('name' => 'decimal'),
103 | 'datetime' => array('name' => 'datetime'),
104 | 'timestamp' => array('name' => 'datetime'),
105 | 'time' => array('name' => 'time'),
106 | 'date' => array('name' => 'date'),
107 | 'binary' => array('name' => 'blob'),
108 | 'boolean' => array('name' => 'boolean')
109 | );
110 | }
111 |
112 | }
113 | ?>
--------------------------------------------------------------------------------
/lib/cache/Memcache.php:
--------------------------------------------------------------------------------
1 |
16 | * host: host for the memcache server
17 | * port: port for the memcache server
18 | *
19 | * @param array $options
20 | */
21 | public function __construct($options)
22 | {
23 | $this->memcache = new \Memcache();
24 | $options['port'] = isset($options['port']) ? $options['port'] : self::DEFAULT_PORT;
25 |
26 | if (!@$this->memcache->connect($options['host'], $options['port'])) {
27 | if ($error = error_get_last()) {
28 | $message = $error['message'];
29 | } else {
30 | $message = sprintf('Could not connect to %s:%s', $options['host'], $options['port']);
31 | }
32 | throw new CacheException($message);
33 | }
34 | }
35 |
36 | public function flush()
37 | {
38 | $this->memcache->flush();
39 | }
40 |
41 | public function read($key)
42 | {
43 | return $this->memcache->get($key);
44 | }
45 |
46 | public function write($key, $value, $expire)
47 | {
48 | $this->memcache->set($key,$value,null,$expire);
49 | }
50 |
51 | public function delete($key)
52 | {
53 | $this->memcache->delete($key);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 | ./test/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/ActiveRecordCacheTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('The memcache extension is not available');
11 | return;
12 | }
13 |
14 | parent::set_up($connection_name);
15 | ActiveRecord\Config::instance()->set_cache('memcache://localhost');
16 | }
17 |
18 | public function tear_down()
19 | {
20 | Cache::flush();
21 | Cache::initialize(null);
22 | }
23 |
24 | public function test_default_expire()
25 | {
26 | $this->assert_equals(30,Cache::$options['expire']);
27 | }
28 |
29 | public function test_explicit_default_expire()
30 | {
31 | ActiveRecord\Config::instance()->set_cache('memcache://localhost',array('expire' => 1));
32 | $this->assert_equals(1,Cache::$options['expire']);
33 | }
34 |
35 | public function test_caches_column_meta_data()
36 | {
37 | Author::first();
38 |
39 | $table_name = Author::table()->get_fully_qualified_table_name(!($this->conn instanceof ActiveRecord\PgsqlAdapter));
40 | $value = Cache::$adapter->read("get_meta_data-$table_name");
41 | $this->assert_true(is_array($value));
42 | }
43 | }
44 | ?>
45 |
--------------------------------------------------------------------------------
/test/CacheModelTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('The memcache extension is not available');
11 | return;
12 | }
13 | parent::set_up($connection_name);
14 | ActiveRecord\Config::instance()->set_cache('memcache://localhost');
15 | }
16 |
17 | protected static function set_method_public($className, $methodName)
18 | {
19 | $class = new ReflectionClass($className);
20 | $method = $class->getMethod($methodName);
21 | $method->setAccessible(true);
22 | return $method;
23 | }
24 |
25 | public function tear_down()
26 | {
27 | Cache::flush();
28 | Cache::initialize(null);
29 | }
30 |
31 | public function test_default_expire()
32 | {
33 | $this->assert_equals(30,Author::table()->cache_model_expire);
34 | }
35 |
36 | public function test_explicit_expire()
37 | {
38 | $this->assert_equals(2592000,Publisher::table()->cache_model_expire);
39 | }
40 |
41 | public function test_cache_key()
42 | {
43 | $method = $this->set_method_public('Author', 'cache_key');
44 | $author = Author::first();
45 |
46 | $this->assert_equals("Author-1", $method->invokeArgs($author, array()));
47 | }
48 |
49 | public function test_model_cache_find_by_pk()
50 | {
51 | $publisher = Publisher::find(1);
52 | $method = $this->set_method_public('Publisher', 'cache_key');
53 | $cache_key = $method->invokeArgs($publisher, array());
54 | $from_cache = Cache::$adapter->read($cache_key);
55 |
56 | $this->assertEquals($publisher->name, $from_cache->name);
57 | }
58 |
59 | public function test_model_cache_new()
60 | {
61 | $publisher = new Publisher(array(
62 | 'name' => 'HarperCollins'
63 | ));
64 | $publisher->save();
65 |
66 | $method = $this->set_method_public('Publisher', 'cache_key');
67 | $cache_key = $method->invokeArgs($publisher, array());
68 |
69 | // Model is cached on first find
70 | $actual = Publisher::find($publisher->id);
71 | $from_cache = Cache::$adapter->read($cache_key);
72 |
73 | $this->assertEquals($actual, $from_cache);
74 | }
75 |
76 | public function test_model_cache_find()
77 | {
78 | $method = $this->set_method_public('Publisher', 'cache_key');
79 | $publishers = Publisher::all();
80 |
81 | foreach($publishers as $publisher)
82 | {
83 | $cache_key = $method->invokeArgs($publisher, array());
84 | $from_cache = Cache::$adapter->read($cache_key);
85 |
86 | $this->assertEquals($publisher->name, $from_cache->name);
87 | }
88 | }
89 |
90 | public function test_regular_models_not_cached()
91 | {
92 | $method = $this->set_method_public('Author', 'cache_key');
93 | $author = Author::first();
94 | $cache_key = $method->invokeArgs($author, array());
95 | $this->assertFalse(Cache::$adapter->read($cache_key));
96 | }
97 |
98 | public function test_model_delete_from_cache()
99 | {
100 | $method = $this->set_method_public('Publisher', 'cache_key');
101 | $publisher = Publisher::find(1);
102 | $cache_key = $method->invokeArgs($publisher, array());
103 |
104 | $publisher->delete();
105 |
106 | // at this point, the cached record should be gone
107 | $this->assertFalse(Cache::$adapter->read($cache_key));
108 |
109 | }
110 |
111 | public function test_model_update_cache(){
112 | $method = $this->set_method_public('Publisher', 'cache_key');
113 |
114 | $publisher = Publisher::find(1);
115 | $cache_key = $method->invokeArgs($publisher, array());
116 | $this->assertEquals('Random House', $publisher->name);
117 |
118 | $from_cache = Cache::$adapter->read($cache_key);
119 | $this->assertEquals('Random House', $from_cache->name);
120 |
121 | // make sure that updates make it to cache
122 | $publisher->name = 'Puppy Publishing';
123 | $publisher->save();
124 |
125 | $actual = Publisher::find($publisher->id);
126 | $from_cache = Cache::$adapter->read($cache_key);
127 |
128 | $this->assertEquals('Puppy Publishing', $from_cache->name);
129 | }
130 |
131 | public function test_model_reload_expires_cache(){
132 | $method = $this->set_method_public('Publisher', 'cache_key');
133 |
134 | $publisher = Publisher::find(1);
135 | $cache_key = $method->invokeArgs($publisher, array());
136 | $this->assertEquals('Random House', $publisher->name);
137 |
138 | // Raw query to not update model properties
139 | Publisher::query('UPDATE publishers SET name = ? WHERE publisher_id = ?', array('Specific House', 1));
140 |
141 | $publisher->reload();
142 |
143 | $this->assertEquals('Specific House', $publisher->name);
144 |
145 | $from_cache = Cache::$adapter->read($cache_key);
146 |
147 | $this->assertEquals('Specific House', $from_cache->name);
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/test/CacheTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('The memcache extension is not available');
11 | return;
12 | }
13 |
14 | Cache::initialize('memcache://localhost');
15 | }
16 |
17 | public function tear_down()
18 | {
19 | Cache::flush();
20 | }
21 |
22 | private function cache_get()
23 | {
24 | return Cache::get("1337", function() { return "abcd"; });
25 | }
26 |
27 | public function test_initialize()
28 | {
29 | $this->assert_not_null(Cache::$adapter);
30 | }
31 |
32 | public function test_initialize_with_null()
33 | {
34 | Cache::initialize(null);
35 | $this->assert_null(Cache::$adapter);
36 | }
37 |
38 | public function test_get_returns_the_value()
39 | {
40 | $this->assert_equals("abcd", $this->cache_get());
41 | }
42 |
43 | public function test_get_writes_to_the_cache()
44 | {
45 | $this->cache_get();
46 | $this->assert_equals("abcd", Cache::$adapter->read("1337"));
47 | }
48 |
49 | public function test_get_does_not_execute_closure_on_cache_hit()
50 | {
51 | $this->cache_get();
52 | Cache::get("1337", function() { throw new Exception("I better not execute!"); });
53 | }
54 |
55 | public function test_cache_adapter_returns_false_on_cache_miss()
56 | {
57 | $this->assert_same(false, Cache::$adapter->read("some-key"));
58 | }
59 |
60 | public function test_get_works_without_caching_enabled()
61 | {
62 | Cache::$adapter = null;
63 | $this->assert_equals("abcd", $this->cache_get());
64 | }
65 |
66 | public function test_cache_expire()
67 | {
68 | Cache::$options['expire'] = 1;
69 | $this->cache_get();
70 | sleep(2);
71 |
72 | $this->assert_same(false, Cache::$adapter->read("1337"));
73 | }
74 |
75 | public function test_namespace_is_set_properly()
76 | {
77 | Cache::$options['namespace'] = 'myapp';
78 | $this->cache_get();
79 | $this->assert_same("abcd", Cache::$adapter->read("myapp::1337"));
80 | }
81 |
82 | /**
83 | * @expectedException ActiveRecord\CacheException
84 | * @expectedExceptionMessage Connection refused
85 | */
86 | public function test_exception_when_connect_fails()
87 | {
88 | Cache::initialize('memcache://127.0.0.1:1234');
89 | }
90 | }
91 | ?>
92 |
--------------------------------------------------------------------------------
/test/ColumnTest.php:
--------------------------------------------------------------------------------
1 | column = new Column();
12 | try {
13 | $this->conn = ActiveRecord\ConnectionManager::get_connection(ActiveRecord\Config::instance()->get_default_connection());
14 | } catch (DatabaseException $e) {
15 | $this->mark_test_skipped('failed to connect using default connection. '.$e->getMessage());
16 | }
17 | }
18 |
19 | public function assert_mapped_type($type, $raw_type)
20 | {
21 | $this->column->raw_type = $raw_type;
22 | $this->assert_equals($type,$this->column->map_raw_type());
23 | }
24 |
25 | public function assert_cast($type, $casted_value, $original_value)
26 | {
27 | $this->column->type = $type;
28 | $value = $this->column->cast($original_value,$this->conn);
29 |
30 | if ($original_value != null && ($type == Column::DATETIME || $type == Column::DATE))
31 | $this->assert_true($value instanceof DateTime);
32 | else
33 | $this->assert_same($casted_value,$value);
34 | }
35 |
36 | public function test_map_raw_type_dates()
37 | {
38 | $this->assert_mapped_type(Column::DATETIME,'datetime');
39 | $this->assert_mapped_type(Column::DATE,'date');
40 | }
41 |
42 | public function test_map_raw_type_integers()
43 | {
44 | $this->assert_mapped_type(Column::INTEGER,'integer');
45 | $this->assert_mapped_type(Column::INTEGER,'int');
46 | $this->assert_mapped_type(Column::INTEGER,'tinyint');
47 | $this->assert_mapped_type(Column::INTEGER,'smallint');
48 | $this->assert_mapped_type(Column::INTEGER,'mediumint');
49 | $this->assert_mapped_type(Column::INTEGER,'bigint');
50 | }
51 |
52 | public function test_map_raw_type_decimals()
53 | {
54 | $this->assert_mapped_type(Column::DECIMAL,'float');
55 | $this->assert_mapped_type(Column::DECIMAL,'double');
56 | $this->assert_mapped_type(Column::DECIMAL,'numeric');
57 | $this->assert_mapped_type(Column::DECIMAL,'dec');
58 | }
59 |
60 | public function test_map_raw_type_strings()
61 | {
62 | $this->assert_mapped_type(Column::STRING,'string');
63 | $this->assert_mapped_type(Column::STRING,'varchar');
64 | $this->assert_mapped_type(Column::STRING,'text');
65 | }
66 |
67 | public function test_map_raw_type_default_to_string()
68 | {
69 | $this->assert_mapped_type(Column::STRING,'bajdslfjasklfjlksfd');
70 | }
71 |
72 | public function test_map_raw_type_changes_integer_to_int()
73 | {
74 | $this->column->raw_type = 'integer';
75 | $this->column->map_raw_type();
76 | $this->assert_equals('int',$this->column->raw_type);
77 | }
78 |
79 | public function test_cast()
80 | {
81 | $datetime = new DateTime('2001-01-01');
82 | $this->assert_cast(Column::INTEGER,1,'1');
83 | $this->assert_cast(Column::INTEGER,1,'1.5');
84 | $this->assert_cast(Column::DECIMAL,1.5,'1.5');
85 | $this->assert_cast(Column::DATETIME,$datetime,'2001-01-01');
86 | $this->assert_cast(Column::DATE,$datetime,'2001-01-01');
87 | $this->assert_cast(Column::DATE,$datetime,$datetime);
88 | $this->assert_cast(Column::STRING,'bubble tea','bubble tea');
89 | $this->assert_cast(Column::INTEGER,4294967295,'4294967295');
90 | $this->assert_cast(Column::INTEGER,'18446744073709551615','18446744073709551615');
91 |
92 | // 32 bit
93 | if (PHP_INT_SIZE === 4)
94 | $this->assert_cast(Column::INTEGER,'2147483648',(((float) PHP_INT_MAX) + 1));
95 | // 64 bit
96 | elseif (PHP_INT_SIZE === 8)
97 | $this->assert_cast(Column::INTEGER,'9223372036854775808',(((float) PHP_INT_MAX) + 1));
98 | }
99 |
100 | public function test_cast_leave_null_alone()
101 | {
102 | $types = array(
103 | Column::STRING,
104 | Column::INTEGER,
105 | Column::DECIMAL,
106 | Column::DATETIME,
107 | Column::DATE);
108 |
109 | foreach ($types as $type) {
110 | $this->assert_cast($type,null,null);
111 | }
112 | }
113 |
114 | public function test_empty_and_null_date_strings_should_return_null()
115 | {
116 | $column = new Column();
117 | $column->type = Column::DATE;
118 | $this->assert_equals(null,$column->cast(null,$this->conn));
119 | $this->assert_equals(null,$column->cast('',$this->conn));
120 | }
121 |
122 | public function test_empty_and_null_datetime_strings_should_return_null()
123 | {
124 | $column = new Column();
125 | $column->type = Column::DATETIME;
126 | $this->assert_equals(null,$column->cast(null,$this->conn));
127 | $this->assert_equals(null,$column->cast('',$this->conn));
128 | }
129 |
130 | public function test_native_date_time_attribute_copies_exact_tz()
131 | {
132 | $dt = new \DateTime(null, new \DateTimeZone('America/New_York'));
133 |
134 | $column = new Column();
135 | $column->type = Column::DATETIME;
136 |
137 | $dt2 = $column->cast($dt, $this->conn);
138 |
139 | $this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());
140 | $this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());
141 | $this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());
142 | }
143 |
144 | public function test_ar_date_time_attribute_copies_exact_tz()
145 | {
146 | $dt = new DateTime(null, new \DateTimeZone('America/New_York'));
147 |
148 | $column = new Column();
149 | $column->type = Column::DATETIME;
150 |
151 | $dt2 = $column->cast($dt, $this->conn);
152 |
153 | $this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());
154 | $this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());
155 | $this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());
156 | }
157 | }
158 | ?>
159 |
--------------------------------------------------------------------------------
/test/ConfigTest.php:
--------------------------------------------------------------------------------
1 | config = new Config();
27 | $this->connections = array('development' => 'mysql://blah/development', 'test' => 'mysql://blah/test');
28 | $this->config->set_connections($this->connections);
29 | }
30 |
31 | /**
32 | * @expectedException ActiveRecord\ConfigException
33 | */
34 | public function test_set_connections_must_be_array()
35 | {
36 | $this->config->set_connections(null);
37 | }
38 |
39 | public function test_get_connections()
40 | {
41 | $this->assert_equals($this->connections,$this->config->get_connections());
42 | }
43 |
44 | public function test_get_connection()
45 | {
46 | $this->assert_equals($this->connections['development'],$this->config->get_connection('development'));
47 | }
48 |
49 | public function test_get_invalid_connection()
50 | {
51 | $this->assert_null($this->config->get_connection('whiskey tango foxtrot'));
52 | }
53 |
54 | public function test_get_default_connection_and_connection()
55 | {
56 | $this->config->set_default_connection('development');
57 | $this->assert_equals('development',$this->config->get_default_connection());
58 | $this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());
59 | }
60 |
61 | public function test_get_default_connection_and_connection_string_defaults_to_development()
62 | {
63 | $this->assert_equals('development',$this->config->get_default_connection());
64 | $this->assert_equals($this->connections['development'],$this->config->get_default_connection_string());
65 | }
66 |
67 | public function test_get_default_connection_string_when_connection_name_is_not_valid()
68 | {
69 | $this->config->set_default_connection('little mac');
70 | $this->assert_null($this->config->get_default_connection_string());
71 | }
72 |
73 | public function test_default_connection_is_set_when_only_one_connection_is_present()
74 | {
75 | $this->config->set_connections(array('development' => $this->connections['development']));
76 | $this->assert_equals('development',$this->config->get_default_connection());
77 | }
78 |
79 | public function test_set_connections_with_default()
80 | {
81 | $this->config->set_connections($this->connections,'test');
82 | $this->assert_equals('test',$this->config->get_default_connection());
83 | }
84 |
85 | public function test_get_date_class_with_default()
86 | {
87 | $this->assert_equals('ActiveRecord\\DateTime', $this->config->get_date_class());
88 | }
89 |
90 | /**
91 | * @expectedException ActiveRecord\ConfigException
92 | */
93 | public function test_set_date_class_when_class_doesnt_exist()
94 | {
95 | $this->config->set_date_class('doesntexist');
96 | }
97 |
98 | /**
99 | * @expectedException ActiveRecord\ConfigException
100 | */
101 | public function test_set_date_class_when_class_doesnt_have_format_or_createfromformat()
102 | {
103 | $this->config->set_date_class('TestLogger');
104 | }
105 |
106 | /**
107 | * @expectedException ActiveRecord\ConfigException
108 | */
109 | public function test_set_date_class_when_class_doesnt_have_createfromformat()
110 | {
111 | $this->config->set_date_class('TestDateTimeWithoutCreateFromFormat');
112 | }
113 |
114 | public function test_set_date_class_with_valid_class()
115 | {
116 | $this->config->set_date_class('TestDateTime');
117 | $this->assert_equals('TestDateTime', $this->config->get_date_class());
118 | }
119 |
120 | public function test_initialize_closure()
121 | {
122 | $test = $this;
123 |
124 | Config::initialize(function($cfg) use ($test)
125 | {
126 | $test->assert_not_null($cfg);
127 | $test->assert_equals('ActiveRecord\Config',get_class($cfg));
128 | });
129 | }
130 |
131 | public function test_logger_object_must_implement_log_method()
132 | {
133 | try {
134 | $this->config->set_logger(new TestLogger);
135 | $this->fail();
136 | } catch (ConfigException $e) {
137 | $this->assert_equals($e->getMessage(), "Logger object must implement a public log method");
138 | }
139 | }
140 | }
141 | ?>
142 |
--------------------------------------------------------------------------------
/test/ConnectionManagerTest.php:
--------------------------------------------------------------------------------
1 | assert_not_null(ConnectionManager::get_connection(null));
11 | $this->assert_not_null(ConnectionManager::get_connection());
12 | }
13 |
14 | public function test_get_connection()
15 | {
16 | $this->assert_not_null(ConnectionManager::get_connection('mysql'));
17 | }
18 |
19 | public function test_get_connection_uses_existing_object()
20 | {
21 | $connection = ConnectionManager::get_connection('mysql');
22 | $this->assert_same($connection, ConnectionManager::get_connection('mysql'));
23 | }
24 |
25 | public function test_get_connection_with_default()
26 | {
27 | $default = ActiveRecord\Config::instance()->get_default_connection('mysql');
28 | $connection = ConnectionManager::get_connection();
29 | $this->assert_same(ConnectionManager::get_connection($default), $connection);
30 | }
31 |
32 | public function test_gh_91_get_connection_with_null_connection_is_always_default()
33 | {
34 | $conn_one = ConnectionManager::get_connection('mysql');
35 | $conn_two = ConnectionManager::get_connection();
36 | $conn_three = ConnectionManager::get_connection('mysql');
37 | $conn_four = ConnectionManager::get_connection();
38 |
39 | $this->assert_same($conn_one, $conn_three);
40 | $this->assert_same($conn_two, $conn_three);
41 | $this->assert_same($conn_four, $conn_three);
42 | }
43 |
44 | public function test_drop_connection()
45 | {
46 | $connection = ConnectionManager::get_connection('mysql');
47 | ConnectionManager::drop_connection('mysql');
48 | $this->assert_not_same($connection, ConnectionManager::get_connection('mysql'));
49 | }
50 |
51 | public function test_drop_connection_with_default()
52 | {
53 | $connection = ConnectionManager::get_connection();
54 | ConnectionManager::drop_connection();
55 | $this->assert_not_same($connection, ConnectionManager::get_connection());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/ConnectionTest.php:
--------------------------------------------------------------------------------
1 | assert_equals('mysql',$info->protocol);
22 | $this->assert_equals('user',$info->user);
23 | $this->assert_equals('pass',$info->pass);
24 | $this->assert_equals('127.0.0.1',$info->host);
25 | $this->assert_equals(3306,$info->port);
26 | $this->assert_equals('dbname',$info->db);
27 | }
28 |
29 | public function test_gh_103_sqlite_connection_string_relative()
30 | {
31 | $info = ActiveRecord\Connection::parse_connection_url('sqlite://../some/path/to/file.db');
32 | $this->assert_equals('../some/path/to/file.db', $info->host);
33 | }
34 |
35 | /**
36 | * @expectedException ActiveRecord\DatabaseException
37 | */
38 | public function test_gh_103_sqlite_connection_string_absolute()
39 | {
40 | $info = ActiveRecord\Connection::parse_connection_url('sqlite:///some/path/to/file.db');
41 | }
42 |
43 | public function test_gh_103_sqlite_connection_string_unix()
44 | {
45 | $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)');
46 | $this->assert_equals('/some/path/to/file.db', $info->host);
47 |
48 | $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/');
49 | $this->assert_equals('/some/path/to/file.db', $info->host);
50 |
51 | $info = ActiveRecord\Connection::parse_connection_url('sqlite://unix(/some/path/to/file.db)/dummy');
52 | $this->assert_equals('/some/path/to/file.db', $info->host);
53 | }
54 |
55 | public function test_gh_103_sqlite_connection_string_windows()
56 | {
57 | $info = ActiveRecord\Connection::parse_connection_url('sqlite://windows(c%3A/some/path/to/file.db)');
58 | $this->assert_equals('c:/some/path/to/file.db', $info->host);
59 | }
60 |
61 | public function test_parse_connection_url_with_unix_sockets()
62 | {
63 | $info = ActiveRecord\Connection::parse_connection_url('mysql://user:password@unix(/tmp/mysql.sock)/database');
64 | $this->assert_equals('/tmp/mysql.sock',$info->host);
65 | }
66 |
67 | public function test_parse_connection_url_with_decode_option()
68 | {
69 | $info = ActiveRecord\Connection::parse_connection_url('mysql://h%20az:h%40i@127.0.0.1/test?decode=true');
70 | $this->assert_equals('h az',$info->user);
71 | $this->assert_equals('h@i',$info->pass);
72 | }
73 |
74 | public function test_encoding()
75 | {
76 | $info = ActiveRecord\Connection::parse_connection_url('mysql://test:test@127.0.0.1/test?charset=utf8');
77 | $this->assert_equals('utf8', $info->charset);
78 | }
79 | }
80 | ?>
81 |
--------------------------------------------------------------------------------
/test/DateFormatTest.php:
--------------------------------------------------------------------------------
1 | some_date = new DateTime();
11 | $author->save();
12 |
13 | $author = Author::first();
14 | $this->assert_is_a("ActiveRecord\\DateTime",$author->some_date);
15 | }
16 |
17 | };
18 | ?>
19 |
--------------------------------------------------------------------------------
/test/DateTimeTest.php:
--------------------------------------------------------------------------------
1 | date = new DateTime();
10 | $this->original_format = DateTime::$DEFAULT_FORMAT;
11 | }
12 |
13 | public function tear_down()
14 | {
15 | DateTime::$DEFAULT_FORMAT = $this->original_format;
16 | }
17 |
18 | private function get_model()
19 | {
20 | try {
21 | $model = new Author();
22 | } catch (DatabaseException $e) {
23 | $this->mark_test_skipped('failed to connect. '.$e->getMessage());
24 | }
25 |
26 | return $model;
27 | }
28 |
29 | private function assert_dirtifies($method /*, method params, ...*/)
30 | {
31 | $model = $this->get_model();
32 | $datetime = new DateTime();
33 | $datetime->attribute_of($model,'some_date');
34 |
35 | $args = func_get_args();
36 | array_shift($args);
37 |
38 | call_user_func_array(array($datetime,$method),$args);
39 | $this->assert_has_keys('some_date', $model->dirty_attributes());
40 | }
41 |
42 | public function test_should_flag_the_attribute_dirty()
43 | {
44 | $interval = new DateInterval('PT1S');
45 | $timezone = new DateTimeZone('America/New_York');
46 | $this->assert_dirtifies('setDate',2001,1,1);
47 | $this->assert_dirtifies('setISODate',2001,1);
48 | $this->assert_dirtifies('setTime',1,1);
49 | $this->assert_dirtifies('setTimestamp',1);
50 | $this->assert_dirtifies('setTimezone',$timezone);
51 | $this->assert_dirtifies('modify','+1 day');
52 | $this->assert_dirtifies('add',$interval);
53 | $this->assert_dirtifies('sub',$interval);
54 | }
55 |
56 | public function test_set_iso_date()
57 | {
58 | $a = new \DateTime();
59 | $a->setISODate(2001,1);
60 |
61 | $b = new DateTime();
62 | $b->setISODate(2001,1);
63 |
64 | $this->assert_datetime_equals($a,$b);
65 | }
66 |
67 | public function test_set_time()
68 | {
69 | $a = new \DateTime();
70 | $a->setTime(1,1);
71 |
72 | $b = new DateTime();
73 | $b->setTime(1,1);
74 |
75 | $this->assert_datetime_equals($a,$b);
76 | }
77 |
78 | public function test_set_time_microseconds()
79 | {
80 | $a = new \DateTime();
81 | $a->setTime(1, 1, 1);
82 |
83 | $b = new DateTime();
84 | $b->setTime(1, 1, 1, 0);
85 |
86 | $this->assert_datetime_equals($a,$b);
87 | }
88 |
89 | public function test_get_format_with_friendly()
90 | {
91 | $this->assert_equals('Y-m-d H:i:s', DateTime::get_format('db'));
92 | }
93 |
94 | public function test_get_format_with_format()
95 | {
96 | $this->assert_equals('Y-m-d', DateTime::get_format('Y-m-d'));
97 | }
98 |
99 | public function test_get_format_with_null()
100 | {
101 | $this->assert_equals(\DateTime::RFC2822, DateTime::get_format());
102 | }
103 |
104 | public function test_format()
105 | {
106 | $this->assert_true(is_string($this->date->format()));
107 | $this->assert_true(is_string($this->date->format('Y-m-d')));
108 | }
109 |
110 | public function test_format_by_friendly_name()
111 | {
112 | $d = date(DateTime::get_format('db'));
113 | $this->assert_equals($d, $this->date->format('db'));
114 | }
115 |
116 | public function test_format_by_custom_format()
117 | {
118 | $format = 'Y/m/d';
119 | $this->assert_equals(date($format), $this->date->format($format));
120 | }
121 |
122 | public function test_format_uses_default()
123 | {
124 | $d = date(DateTime::$FORMATS[DateTime::$DEFAULT_FORMAT]);
125 | $this->assert_equals($d, $this->date->format());
126 | }
127 |
128 | public function test_all_formats()
129 | {
130 | foreach (DateTime::$FORMATS as $name => $format)
131 | $this->assert_equals(date($format), $this->date->format($name));
132 | }
133 |
134 | public function test_change_default_format_to_format_string()
135 | {
136 | DateTime::$DEFAULT_FORMAT = 'H:i:s';
137 | $this->assert_equals(date(DateTime::$DEFAULT_FORMAT), $this->date->format());
138 | }
139 |
140 | public function test_change_default_format_to_friently()
141 | {
142 | DateTime::$DEFAULT_FORMAT = 'short';
143 | $this->assert_equals(date(DateTime::$FORMATS['short']), $this->date->format());
144 | }
145 |
146 | public function test_to_string()
147 | {
148 | $this->assert_equals(date(DateTime::get_format()), "" . $this->date);
149 | }
150 |
151 | public function test_create_from_format_error_handling()
152 | {
153 | $d = DateTime::createFromFormat('H:i:s Y-d-m', '!!!');
154 | $this->assert_false($d);
155 | }
156 |
157 | public function test_create_from_format_without_tz()
158 | {
159 | $d = DateTime::createFromFormat('H:i:s Y-d-m', '03:04:05 2000-02-01');
160 | $this->assert_equals(new DateTime('2000-01-02 03:04:05'), $d);
161 | }
162 |
163 | public function test_create_from_format_with_tz()
164 | {
165 | $d = DateTime::createFromFormat('Y-m-d H:i:s', '2000-02-01 03:04:05', new \DateTimeZone('Etc/GMT-10'));
166 | $d2 = new DateTime('2000-01-31 17:04:05');
167 |
168 | $this->assert_equals($d2->getTimestamp(), $d->getTimestamp());
169 | }
170 |
171 | public function test_native_date_time_attribute_copies_exact_tz()
172 | {
173 | $dt = new \DateTime(null, new \DateTimeZone('America/New_York'));
174 | $model = $this->get_model();
175 |
176 | // Test that the data transforms without modification
177 | $model->assign_attribute('updated_at', $dt);
178 | $dt2 = $model->read_attribute('updated_at');
179 |
180 | $this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());
181 | $this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());
182 | $this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());
183 | }
184 |
185 | public function test_ar_date_time_attribute_copies_exact_tz()
186 | {
187 | $dt = new DateTime(null, new \DateTimeZone('America/New_York'));
188 | $model = $this->get_model();
189 |
190 | // Test that the data transforms without modification
191 | $model->assign_attribute('updated_at', $dt);
192 | $dt2 = $model->read_attribute('updated_at');
193 |
194 | $this->assert_equals($dt->getTimestamp(), $dt2->getTimestamp());
195 | $this->assert_equals($dt->getTimeZone(), $dt2->getTimeZone());
196 | $this->assert_equals($dt->getTimeZone()->getName(), $dt2->getTimeZone()->getName());
197 | }
198 |
199 | public function test_clone()
200 | {
201 | $model = $this->get_model();
202 | $model_attribute = 'some_date';
203 |
204 | $datetime = new DateTime();
205 | $datetime->attribute_of($model, $model_attribute);
206 |
207 | $cloned_datetime = clone $datetime;
208 |
209 | // Assert initial state
210 | $this->assert_false($model->attribute_is_dirty($model_attribute));
211 |
212 | $cloned_datetime->add(new DateInterval('PT1S'));
213 |
214 | // Assert that modifying the cloned object didn't flag the model
215 | $this->assert_false($model->attribute_is_dirty($model_attribute));
216 |
217 | $datetime->add(new DateInterval('PT1S'));
218 |
219 | // Assert that modifying the model-attached object did flag the model
220 | $this->assert_true($model->attribute_is_dirty($model_attribute));
221 |
222 | // Assert that the dates are equal but not the same instance
223 | $this->assert_equals($datetime, $cloned_datetime);
224 | $this->assert_not_same($datetime, $cloned_datetime);
225 | }
226 | }
227 | ?>
228 |
--------------------------------------------------------------------------------
/test/ExpressionsTest.php:
--------------------------------------------------------------------------------
1 | assert_equals(array(1,2), $c->values());
14 | }
15 |
16 | public function test_one_variable()
17 | {
18 | $c = new Expressions(null,'name=?','Tito');
19 | $this->assert_equals('name=?',$c->to_s());
20 | $this->assert_equals(array('Tito'),$c->values());
21 | }
22 |
23 | public function test_array_variable()
24 | {
25 | $c = new Expressions(null,'name IN(?) and id=?',array('Tito','George'),1);
26 | $this->assert_equals(array(array('Tito','George'),1),$c->values());
27 | }
28 |
29 | public function test_multiple_variables()
30 | {
31 | $c = new Expressions(null,'name=? and book=?','Tito','Sharks');
32 | $this->assert_equals('name=? and book=?',$c->to_s());
33 | $this->assert_equals(array('Tito','Sharks'),$c->values());
34 | }
35 |
36 | public function test_to_string()
37 | {
38 | $c = new Expressions(null,'name=? and book=?','Tito','Sharks');
39 | $this->assert_equals('name=? and book=?',$c->to_s());
40 | }
41 |
42 | public function test_to_string_with_array_variable()
43 | {
44 | $c = new Expressions(null,'name IN(?) and id=?',array('Tito','George'),1);
45 | $this->assert_equals('name IN(?,?) and id=?',$c->to_s());
46 | }
47 |
48 | public function test_to_string_with_null_options()
49 | {
50 | $c = new Expressions(null,'name=? and book=?','Tito','Sharks');
51 | $x = null;
52 | $this->assert_equals('name=? and book=?',$c->to_s(false,$x));
53 | }
54 |
55 | /**
56 | * @expectedException ActiveRecord\ExpressionsException
57 | */
58 | public function test_insufficient_variables()
59 | {
60 | $c = new Expressions(null,'name=? and id=?','Tito');
61 | $c->to_s();
62 | }
63 |
64 | public function test_no_values()
65 | {
66 | $c = new Expressions(null,"name='Tito'");
67 | $this->assert_equals("name='Tito'",$c->to_s());
68 | $this->assert_equals(0,count($c->values()));
69 | }
70 |
71 | public function test_null_variable()
72 | {
73 | $a = new Expressions(null,'name=?',null);
74 | $this->assert_equals('name=?',$a->to_s());
75 | $this->assert_equals(array(null),$a->values());
76 | }
77 |
78 | public function test_zero_variable()
79 | {
80 | $a = new Expressions(null,'name=?',0);
81 | $this->assert_equals('name=?',$a->to_s());
82 | $this->assert_equals(array(0),$a->values());
83 | }
84 |
85 | public function test_empty_array_variable()
86 | {
87 | $a = new Expressions(null,'id IN(?)',array());
88 | $this->assert_equals('id IN(?)',$a->to_s());
89 | $this->assert_equals(array(array()),$a->values());
90 | }
91 |
92 | public function test_ignore_invalid_parameter_marker()
93 | {
94 | $a = new Expressions(null,"question='Do you love backslashes?' and id in(?)",array(1,2));
95 | $this->assert_equals("question='Do you love backslashes?' and id in(?,?)",$a->to_s());
96 | }
97 |
98 | public function test_ignore_parameter_marker_with_escaped_quote()
99 | {
100 | $a = new Expressions(null,"question='Do you love''s backslashes?' and id in(?)",array(1,2));
101 | $this->assert_equals("question='Do you love''s backslashes?' and id in(?,?)",$a->to_s());
102 | }
103 |
104 | public function test_ignore_parameter_marker_with_backspace_escaped_quote()
105 | {
106 | $a = new Expressions(null,"question='Do you love\\'s backslashes?' and id in(?)",array(1,2));
107 | $this->assert_equals("question='Do you love\\'s backslashes?' and id in(?,?)",$a->to_s());
108 | }
109 |
110 | public function test_substitute()
111 | {
112 | $a = new Expressions(null,'name=? and id=?','Tito',1);
113 | $this->assert_equals("name='Tito' and id=1",$a->to_s(true));
114 | }
115 |
116 | public function test_substitute_quotes_scalars_but_not_others()
117 | {
118 | $a = new Expressions(null,'id in(?)',array(1,'2',3.5));
119 | $this->assert_equals("id in(1,'2',3.5)",$a->to_s(true));
120 | }
121 |
122 | public function test_substitute_where_value_has_question_mark()
123 | {
124 | $a = new Expressions(null,'name=? and id=?','??????',1);
125 | $this->assert_equals("name='??????' and id=1",$a->to_s(true));
126 | }
127 |
128 | public function test_substitute_array_value()
129 | {
130 | $a = new Expressions(null,'id in(?)',array(1,2));
131 | $this->assert_equals("id in(1,2)",$a->to_s(true));
132 | }
133 |
134 | public function test_substitute_escapes_quotes()
135 | {
136 | $a = new Expressions(null,'name=? or name in(?)',"Tito's Guild",array(1,"Tito's Guild"));
137 | $this->assert_equals("name='Tito''s Guild' or name in(1,'Tito''s Guild')",$a->to_s(true));
138 | }
139 |
140 | public function test_substitute_escape_quotes_with_connections_escape_method()
141 | {
142 | try {
143 | $conn = ConnectionManager::get_connection();
144 | } catch (DatabaseException $e) {
145 | $this->mark_test_skipped('failed to connect. '.$e->getMessage());
146 | }
147 | $a = new Expressions(null,'name=?',"Tito's Guild");
148 | $a->set_connection($conn);
149 | $escaped = $conn->escape("Tito's Guild");
150 | $this->assert_equals("name=$escaped",$a->to_s(true));
151 | }
152 |
153 | public function test_bind()
154 | {
155 | $a = new Expressions(null,'name=? and id=?','Tito');
156 | $a->bind(2,1);
157 | $this->assert_equals(array('Tito',1),$a->values());
158 | }
159 |
160 | public function test_bind_overwrite_existing()
161 | {
162 | $a = new Expressions(null,'name=? and id=?','Tito',1);
163 | $a->bind(2,99);
164 | $this->assert_equals(array('Tito',99),$a->values());
165 | }
166 |
167 | /**
168 | * @expectedException ActiveRecord\ExpressionsException
169 | */
170 | public function test_bind_invalid_parameter_number()
171 | {
172 | $a = new Expressions(null,'name=?');
173 | $a->bind(0,99);
174 | }
175 |
176 | public function test_subsitute_using_alternate_values()
177 | {
178 | $a = new Expressions(null,'name=?','Tito');
179 | $this->assert_equals("name='Tito'",$a->to_s(true));
180 | $x = array('values' => array('Hocus'));
181 | $this->assert_equals("name='Hocus'",$a->to_s(true,$x));
182 | }
183 |
184 | public function test_null_value()
185 | {
186 | $a = new Expressions(null,'name=?',null);
187 | $this->assert_equals('name=NULL',$a->to_s(true));
188 | }
189 |
190 | public function test_hash_with_default_glue()
191 | {
192 | $a = new Expressions(null,array('id' => 1, 'name' => 'Tito'));
193 | $this->assert_equals('id=? AND name=?',$a->to_s());
194 | }
195 |
196 | public function test_hash_with_glue()
197 | {
198 | $a = new Expressions(null,array('id' => 1, 'name' => 'Tito'),', ');
199 | $this->assert_equals('id=?, name=?',$a->to_s());
200 | }
201 |
202 | public function test_hash_with_array()
203 | {
204 | $a = new Expressions(null,array('id' => 1, 'name' => array('Tito','Mexican')));
205 | $this->assert_equals('id=? AND name IN(?,?)',$a->to_s());
206 | }
207 | }
208 | ?>
209 |
--------------------------------------------------------------------------------
/test/HasManyThroughTest.php:
--------------------------------------------------------------------------------
1 | assert_equals($newsletter->id, $user->newsletters[0]->id);
15 | $this->assert_equals(
16 | 'foo\bar\biz\Newsletter',
17 | get_class($user->newsletters[0])
18 | );
19 | $this->assert_equals($user->id, $newsletter->users[0]->id);
20 | $this->assert_equals(
21 | 'foo\bar\biz\User',
22 | get_class($newsletter->users[0])
23 | );
24 | }
25 |
26 | public function test_gh101_has_many_through_include()
27 | {
28 | $user = User::find(1, array(
29 | 'include' => array(
30 | 'user_newsletters'
31 | )
32 | ));
33 |
34 | $this->assert_equals(1, $user->id);
35 | $this->assert_equals(1, $user->user_newsletters[0]->id);
36 | }
37 |
38 | public function test_gh107_has_many_through_include_eager()
39 | {
40 | $venue = Venue::find(1, array('include' => array('events')));
41 | $this->assert_equals(1, $venue->events[0]->id);
42 |
43 | $venue = Venue::find(1, array('include' => array('hosts')));
44 | $this->assert_equals(1, $venue->hosts[0]->id);
45 | }
46 |
47 | public function test_gh107_has_many_though_include_eager_with_namespace()
48 | {
49 | $user = User::find(1, array(
50 | 'include' => array(
51 | 'newsletters'
52 | )
53 | ));
54 |
55 | $this->assert_equals(1, $user->id);
56 | $this->assert_equals(1, $user->newsletters[0]->id);
57 | }
58 | }
59 | # vim: noet ts=4 nobinary
60 | ?>
61 |
--------------------------------------------------------------------------------
/test/InflectorTest.php:
--------------------------------------------------------------------------------
1 | inflector = ActiveRecord\Inflector::instance();
9 | }
10 |
11 | public function test_underscorify()
12 | {
13 | $this->assert_equals('rm__name__bob',$this->inflector->variablize('rm--name bob'));
14 | $this->assert_equals('One_Two_Three',$this->inflector->underscorify('OneTwoThree'));
15 | }
16 |
17 | public function test_tableize()
18 | {
19 | $this->assert_equals('angry_people',$this->inflector->tableize('AngryPerson'));
20 | $this->assert_equals('my_sqls',$this->inflector->tableize('MySQL'));
21 | }
22 |
23 | public function test_keyify()
24 | {
25 | $this->assert_equals('building_type_id', $this->inflector->keyify('BuildingType'));
26 | }
27 | };
28 | ?>
--------------------------------------------------------------------------------
/test/ModelCallbackTest.php:
--------------------------------------------------------------------------------
1 | venue = new Venue();
10 | $this->callback = Venue::table()->callback;
11 | }
12 |
13 | public function register_and_invoke_callbacks($callbacks, $return, $closure)
14 | {
15 | if (!is_array($callbacks))
16 | $callbacks = array($callbacks);
17 |
18 | $fired = array();
19 |
20 | foreach ($callbacks as $name)
21 | $this->callback->register($name,function($model) use (&$fired, $name, $return) { $fired[] = $name; return $return; });
22 |
23 | $closure($this->venue);
24 | return array_intersect($callbacks,$fired);
25 | }
26 |
27 | public function assert_fires($callbacks, $closure)
28 | {
29 | $executed = $this->register_and_invoke_callbacks($callbacks,true,$closure);
30 | $this->assert_equals(count($callbacks),count($executed));
31 | }
32 |
33 | public function assert_does_not_fire($callbacks, $closure)
34 | {
35 | $executed = $this->register_and_invoke_callbacks($callbacks,true,$closure);
36 | $this->assert_equals(0,count($executed));
37 | }
38 |
39 | public function assert_fires_returns_false($callbacks, $only_fire, $closure)
40 | {
41 | if (!is_array($only_fire))
42 | $only_fire = array($only_fire);
43 |
44 | $executed = $this->register_and_invoke_callbacks($callbacks,false,$closure);
45 | sort($only_fire);
46 | $intersect = array_intersect($only_fire,$executed);
47 | sort($intersect);
48 | $this->assert_equals($only_fire,$intersect);
49 | }
50 |
51 | public function test_after_construct_fires_by_default()
52 | {
53 | $this->assert_fires('after_construct',function($model) { new Venue(); });
54 | }
55 |
56 | public function test_fire_validation_callbacks_on_insert()
57 | {
58 | $this->assert_fires(array('before_validation','after_validation','before_validation_on_create','after_validation_on_create'),
59 | function($model) { $model = new Venue(); $model->save(); });
60 | }
61 |
62 | public function test_fire_validation_callbacks_on_update()
63 | {
64 | $this->assert_fires(array('before_validation','after_validation','before_validation_on_update','after_validation_on_update'),
65 | function($model) { $model = Venue::first(); $model->save(); });
66 | }
67 |
68 | public function test_validation_call_backs_not_fired_due_to_bypassing_validations()
69 | {
70 | $this->assert_does_not_fire('before_validation',function($model) { $model->save(false); });
71 | }
72 |
73 | public function test_before_validation_returning_false_cancels_callbacks()
74 | {
75 | $this->assert_fires_returns_false(array('before_validation','after_validation'),'before_validation',
76 | function($model) { $model->save(); });
77 | }
78 |
79 | public function test_fires_before_save_and_before_update_when_updating()
80 | {
81 | $this->assert_fires(array('before_save','before_update'),
82 | function($model) { $model = Venue::first(); $model->name = "something new"; $model->save(); });
83 | }
84 |
85 | public function test_before_save_returning_false_cancels_callbacks()
86 | {
87 | $this->assert_fires_returns_false(array('before_save','before_create'),'before_save',
88 | function($model) { $model = new Venue(); $model->save(); });
89 | }
90 |
91 | public function test_destroy()
92 | {
93 | $this->assert_fires(array('before_destroy','after_destroy'),
94 | function($model) { $model->delete(); });
95 | }
96 | }
97 | ?>
98 |
--------------------------------------------------------------------------------
/test/MysqlAdapterTest.php:
--------------------------------------------------------------------------------
1 | conn->columns('authors');
16 | $this->assert_equals('enum',$author_columns['some_enum']->raw_type);
17 | $this->assert_equals(Column::STRING,$author_columns['some_enum']->type);
18 | $this->assert_same(null,$author_columns['some_enum']->length);
19 | }
20 |
21 | public function test_set_charset()
22 | {
23 | $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
24 | $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
25 | $this->assert_equals('SET NAMES ?',$conn->last_query);
26 | }
27 |
28 | public function test_limit_with_null_offset_does_not_contain_offset()
29 | {
30 | $ret = array();
31 | $sql = 'SELECT * FROM authors ORDER BY name ASC';
32 | $this->conn->query_and_fetch($this->conn->limit($sql,null,1),function($row) use (&$ret) { $ret[] = $row; });
33 |
34 | $this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false);
35 | }
36 | }
37 | ?>
38 |
--------------------------------------------------------------------------------
/test/OciAdapterTest.php:
--------------------------------------------------------------------------------
1 | assert_equals('authors_seq',$this->conn->get_sequence_name('authors','author_id'));
14 | }
15 |
16 | public function test_columns_text()
17 | {
18 | $author_columns = $this->conn->columns('authors');
19 | $this->assert_equals('varchar2',$author_columns['some_text']->raw_type);
20 | $this->assert_equals(100,$author_columns['some_text']->length);
21 | }
22 |
23 | public function test_datetime_to_string()
24 | {
25 | $this->assert_equals('01-Jan-2009 01:01:01 AM',$this->conn->datetime_to_string(date_create('2009-01-01 01:01:01 EST')));
26 | }
27 |
28 | public function test_date_to_string()
29 | {
30 | $this->assert_equals('01-Jan-2009',$this->conn->date_to_string(date_create('2009-01-01 01:01:01 EST')));
31 | }
32 |
33 | public function test_insert_id() {}
34 | public function test_insert_id_with_params() {}
35 | public function test_insert_id_should_return_explicitly_inserted_id() {}
36 | public function test_columns_time() {}
37 | public function test_columns_sequence() {}
38 |
39 | public function test_set_charset()
40 | {
41 | $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
42 | $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
43 | $this->assert_equals(';charset=utf8', $conn->dsn_params);
44 | }
45 | }
46 | ?>
47 |
--------------------------------------------------------------------------------
/test/PgsqlAdapterTest.php:
--------------------------------------------------------------------------------
1 | conn->query("INSERT INTO authors(author_id,name) VALUES(nextval('authors_author_id_seq'),'name')");
16 | $this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);
17 | }
18 |
19 | public function test_insert_id_with_params()
20 | {
21 | $x = array('name');
22 | $this->conn->query("INSERT INTO authors(author_id,name) VALUES(nextval('authors_author_id_seq'),?)",$x);
23 | $this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);
24 | }
25 |
26 | public function test_insert_id_should_return_explicitly_inserted_id()
27 | {
28 | $this->conn->query('INSERT INTO authors(author_id,name) VALUES(99,\'name\')');
29 | $this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0);
30 | }
31 |
32 | public function test_set_charset()
33 | {
34 | $connection_string = ActiveRecord\Config::instance()->get_connection($this->connection_name);
35 | $conn = ActiveRecord\Connection::instance($connection_string . '?charset=utf8');
36 | $this->assert_equals("SET NAMES 'utf8'",$conn->last_query);
37 | }
38 |
39 | public function test_gh96_columns_not_duplicated_by_index()
40 | {
41 | $this->assert_equals(3,$this->conn->query_column_info("user_newsletters")->rowCount());
42 | }
43 | }
44 | ?>
45 |
--------------------------------------------------------------------------------
/test/SerializationTest.php:
--------------------------------------------------------------------------------
1 | to_a();
22 | }
23 |
24 | public function test_only()
25 | {
26 | $this->assert_has_keys('name', 'special', $this->_a(array('only' => array('name', 'special'))));
27 | }
28 |
29 | public function test_only_not_array()
30 | {
31 | $this->assert_has_keys('name', $this->_a(array('only' => 'name')));
32 | }
33 |
34 | public function test_only_should_only_apply_to_attributes()
35 | {
36 | $this->assert_has_keys('name','author', $this->_a(array('only' => 'name', 'include' => 'author')));
37 | $this->assert_has_keys('book_id','upper_name', $this->_a(array('only' => 'book_id', 'methods' => 'upper_name')));
38 | }
39 |
40 | public function test_only_overrides_except()
41 | {
42 | $this->assert_has_keys('name', $this->_a(array('only' => 'name', 'except' => 'name')));
43 | }
44 |
45 | public function test_except()
46 | {
47 | $this->assert_doesnt_has_keys('name', 'special', $this->_a(array('except' => array('name','special'))));
48 | }
49 |
50 | public function test_except_takes_a_string()
51 | {
52 | $this->assert_doesnt_has_keys('name', $this->_a(array('except' => 'name')));
53 | }
54 |
55 | public function test_methods()
56 | {
57 | $a = $this->_a(array('methods' => array('upper_name')));
58 | $this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']);
59 | }
60 |
61 | public function test_methods_takes_a_string()
62 | {
63 | $a = $this->_a(array('methods' => 'upper_name'));
64 | $this->assert_equals('ANCIENT ART OF MAIN TANKING', $a['upper_name']);
65 | }
66 |
67 | // methods added last should we shuld have value of the method in our json
68 | // rather than the regular attribute value
69 | public function test_methods_method_same_as_attribute()
70 | {
71 | $a = $this->_a(array('methods' => 'name'));
72 | $this->assert_equals('ancient art of main tanking', $a['name']);
73 | }
74 |
75 | public function test_include()
76 | {
77 | $a = $this->_a(array('include' => array('author')));
78 | $this->assert_has_keys('parent_author_id', $a['author']);
79 | }
80 |
81 | public function test_include_nested_with_nested_options()
82 | {
83 | $a = $this->_a(
84 | array('include' => array('events' => array('except' => 'title', 'include' => array('host' => array('only' => 'id'))))),
85 | Host::find(4));
86 |
87 | $this->assert_equals(3, count($a['events']));
88 | $this->assert_doesnt_has_keys('title', $a['events'][0]);
89 | $this->assert_equals(array('id' => 4), $a['events'][0]['host']);
90 | }
91 |
92 | public function test_datetime_values_get_converted_to_strings()
93 | {
94 | $now = new DateTime();
95 | $a = $this->_a(array('only' => 'created_at'),new Author(array('created_at' => $now)));
96 | $this->assert_equals($now->format(ActiveRecord\Serialization::$DATETIME_FORMAT),$a['created_at']);
97 | }
98 |
99 | public function test_to_json()
100 | {
101 | $book = Book::find(1);
102 | $json = $book->to_json();
103 | $this->assert_equals($book->attributes(),(array)json_decode($json));
104 | }
105 |
106 | public function test_to_json_include_root()
107 | {
108 | ActiveRecord\JsonSerializer::$include_root = true;
109 | $this->assert_not_null(json_decode(Book::find(1)->to_json())->book);
110 | }
111 |
112 | public function test_to_xml_include()
113 | {
114 | $xml = Host::find(4)->to_xml(array('include' => 'events'));
115 | $decoded = get_object_vars(new SimpleXMLElement($xml));
116 |
117 | $this->assert_equals(3, count($decoded['events']->event));
118 | }
119 |
120 | public function test_to_xml()
121 | {
122 | $book = Book::find(1);
123 | $this->assert_equals($book->attributes(),get_object_vars(new SimpleXMLElement($book->to_xml())));
124 | }
125 |
126 | public function test_to_array()
127 | {
128 | $book = Book::find(1);
129 | $array = $book->to_array();
130 | $this->assert_equals($book->attributes(), $array);
131 | }
132 |
133 | public function test_to_array_include_root()
134 | {
135 | ActiveRecord\ArraySerializer::$include_root = true;
136 | $book = Book::find(1);
137 | $array = $book->to_array();
138 | $book_attributes = array('book' => $book->attributes());
139 | $this->assert_equals($book_attributes, $array);
140 | }
141 |
142 | public function test_to_array_except()
143 | {
144 | $book = Book::find(1);
145 | $array = $book->to_array(array('except' => array('special')));
146 | $book_attributes = $book->attributes();
147 | unset($book_attributes['special']);
148 | $this->assert_equals($book_attributes, $array);
149 | }
150 |
151 | public function test_works_with_datetime()
152 | {
153 | Author::find(1)->update_attribute('created_at',new DateTime());
154 | $this->assert_reg_exp('/[0-9]{4}-[0-9]{2}-[0-9]{2}/',Author::find(1)->to_xml());
155 | $this->assert_reg_exp('/"updated_at":"[0-9]{4}-[0-9]{2}-[0-9]{2}/',Author::find(1)->to_json());
156 | }
157 |
158 | public function test_to_xml_skip_instruct()
159 | {
160 | $this->assert_same(false,strpos(Book::find(1)->to_xml(array('skip_instruct' => true)),'assert_same(0, strpos(Book::find(1)->to_xml(array('skip_instruct' => false)),'assert_contains('lasers', Author::first()->to_xml(array('only_method' => 'return_something')));
167 | }
168 |
169 | public function test_to_csv()
170 | {
171 | $book = Book::find(1);
172 | $this->assert_equals('1,1,2,"Ancient Art of Main Tanking",0,0',$book->to_csv());
173 | }
174 |
175 | public function test_to_csv_only_header()
176 | {
177 | $book = Book::find(1);
178 | $this->assert_equals('book_id,author_id,secondary_author_id,name,numeric_test,special',
179 | $book->to_csv(array('only_header'=>true))
180 | );
181 | }
182 |
183 | public function test_to_csv_only_method()
184 | {
185 | $book = Book::find(1);
186 | $this->assert_equals('2,"Ancient Art of Main Tanking"',
187 | $book->to_csv(array('only'=>array('name','secondary_author_id')))
188 | );
189 | }
190 |
191 | public function test_to_csv_only_method_on_header()
192 | {
193 | $book = Book::find(1);
194 | $this->assert_equals('secondary_author_id,name',
195 | $book->to_csv(array('only'=>array('secondary_author_id','name'),
196 | 'only_header'=>true))
197 | );
198 | }
199 |
200 | public function test_to_csv_with_custom_delimiter()
201 | {
202 | $book = Book::find(1);
203 | ActiveRecord\CsvSerializer::$delimiter=';';
204 | $this->assert_equals('1;1;2;"Ancient Art of Main Tanking";0;0',$book->to_csv());
205 | }
206 |
207 | public function test_to_csv_with_custom_enclosure()
208 | {
209 | $book = Book::find(1);
210 | ActiveRecord\CsvSerializer::$delimiter=',';
211 | ActiveRecord\CsvSerializer::$enclosure="'";
212 | $this->assert_equals("1,1,2,'Ancient Art of Main Tanking',0,0",$book->to_csv());
213 | }
214 | };
215 | ?>
216 |
--------------------------------------------------------------------------------
/test/SqliteAdapterTest.php:
--------------------------------------------------------------------------------
1 | assertFalse(true);
32 | }
33 | catch (ActiveRecord\DatabaseException $e)
34 | {
35 | $this->assertFalse(file_exists(__DIR__ . "/" . self::InvalidDb));
36 | }
37 | }
38 |
39 | public function test_limit_with_null_offset_does_not_contain_offset()
40 | {
41 | $ret = array();
42 | $sql = 'SELECT * FROM authors ORDER BY name ASC';
43 | $this->conn->query_and_fetch($this->conn->limit($sql,null,1),function($row) use (&$ret) { $ret[] = $row; });
44 |
45 | $this->assert_true(strpos($this->conn->last_query, 'LIMIT 1') !== false);
46 | }
47 |
48 | public function test_gh183_sqliteadapter_autoincrement()
49 | {
50 | // defined in lowercase: id integer not null primary key
51 | $columns = $this->conn->columns('awesome_people');
52 | $this->assert_true($columns['id']->auto_increment);
53 |
54 | // defined in uppercase: `amenity_id` INTEGER NOT NULL PRIMARY KEY
55 | $columns = $this->conn->columns('amenities');
56 | $this->assert_true($columns['amenity_id']->auto_increment);
57 |
58 | // defined using int: `rm-id` INT NOT NULL
59 | $columns = $this->conn->columns('`rm-bldg`');
60 | $this->assert_false($columns['rm-id']->auto_increment);
61 |
62 | // defined using int: id INT NOT NULL PRIMARY KEY
63 | $columns = $this->conn->columns('hosts');
64 | $this->assert_true($columns['id']->auto_increment);
65 | }
66 |
67 | public function test_datetime_to_string()
68 | {
69 | $datetime = '2009-01-01 01:01:01';
70 | $this->assert_equals($datetime,$this->conn->datetime_to_string(date_create($datetime)));
71 | }
72 |
73 | public function test_date_to_string()
74 | {
75 | $datetime = '2009-01-01';
76 | $this->assert_equals($datetime,$this->conn->date_to_string(date_create($datetime)));
77 | }
78 |
79 | // not supported
80 | public function test_connect_with_port() {}
81 | }
82 | ?>
--------------------------------------------------------------------------------
/test/UtilsTest.php:
--------------------------------------------------------------------------------
1 | object_array = array(null,null);
10 | $this->object_array[0] = new stdClass();
11 | $this->object_array[0]->a = "0a";
12 | $this->object_array[0]->b = "0b";
13 | $this->object_array[1] = new stdClass();
14 | $this->object_array[1]->a = "1a";
15 | $this->object_array[1]->b = "1b";
16 |
17 | $this->array_hash = array(
18 | array("a" => "0a", "b" => "0b"),
19 | array("a" => "1a", "b" => "1b"));
20 | }
21 |
22 | public function test_collect_with_array_of_objects_using_closure()
23 | {
24 | $this->assert_equals(array("0a","1a"),AR\collect($this->object_array,function($obj) { return $obj->a; }));
25 | }
26 |
27 | public function test_collect_with_array_of_objects_using_string()
28 | {
29 | $this->assert_equals(array("0a","1a"),AR\collect($this->object_array,"a"));
30 | }
31 |
32 | public function test_collect_with_array_hash_using_closure()
33 | {
34 | $this->assert_equals(array("0a","1a"),AR\collect($this->array_hash,function($item) { return $item["a"]; }));
35 | }
36 |
37 | public function test_collect_with_array_hash_using_string()
38 | {
39 | $this->assert_equals(array("0a","1a"),AR\collect($this->array_hash,"a"));
40 | }
41 |
42 | public function test_array_flatten()
43 | {
44 | $this->assert_equals(array(), AR\array_flatten(array()));
45 | $this->assert_equals(array(1), AR\array_flatten(array(1)));
46 | $this->assert_equals(array(1), AR\array_flatten(array(array(1))));
47 | $this->assert_equals(array(1, 2), AR\array_flatten(array(array(1, 2))));
48 | $this->assert_equals(array(1, 2), AR\array_flatten(array(array(1), 2)));
49 | $this->assert_equals(array(1, 2), AR\array_flatten(array(1, array(2))));
50 | $this->assert_equals(array(1, 2, 3), AR\array_flatten(array(1, array(2), 3)));
51 | $this->assert_equals(array(1, 2, 3, 4), AR\array_flatten(array(1, array(2, 3), 4)));
52 | $this->assert_equals(array(1, 2, 3, 4, 5, 6), AR\array_flatten(array(1, array(2, 3), 4, array(5, 6))));
53 | }
54 |
55 | public function test_all()
56 | {
57 | $this->assert_true(AR\all(null,array(null,null)));
58 | $this->assert_true(AR\all(1,array(1,1)));
59 | $this->assert_false(AR\all(1,array(1,'1')));
60 | $this->assert_false(AR\all(null,array('',null)));
61 | }
62 |
63 | public function test_classify()
64 | {
65 | $bad_class_names = array('ubuntu_rox', 'stop_the_Snake_Case', 'CamelCased', 'camelCased');
66 | $good_class_names = array('UbuntuRox', 'StopTheSnakeCase', 'CamelCased', 'CamelCased');
67 |
68 | $class_names = array();
69 | foreach ($bad_class_names as $s)
70 | $class_names[] = AR\classify($s);
71 |
72 | $this->assert_equals($class_names, $good_class_names);
73 | }
74 |
75 | public function test_classify_singularize()
76 | {
77 | $bad_class_names = array('events', 'stop_the_Snake_Cases', 'angry_boxes', 'Mad_Sheep_herders', 'happy_People');
78 | $good_class_names = array('Event', 'StopTheSnakeCase', 'AngryBox', 'MadSheepHerder', 'HappyPerson');
79 |
80 | $class_names = array();
81 | foreach ($bad_class_names as $s)
82 | $class_names[] = AR\classify($s, true);
83 |
84 | $this->assert_equals($class_names, $good_class_names);
85 | }
86 |
87 | public function test_singularize()
88 | {
89 | $this->assert_equals('order_status',AR\Utils::singularize('order_status'));
90 | $this->assert_equals('order_status',AR\Utils::singularize('order_statuses'));
91 | $this->assert_equals('os_type', AR\Utils::singularize('os_type'));
92 | $this->assert_equals('os_type', AR\Utils::singularize('os_types'));
93 | $this->assert_equals('photo', AR\Utils::singularize('photos'));
94 | $this->assert_equals('pass', AR\Utils::singularize('pass'));
95 | $this->assert_equals('pass', AR\Utils::singularize('passes'));
96 | }
97 |
98 | public function test_wrap_strings_in_arrays()
99 | {
100 | $x = array('1',array('2'));
101 | $this->assert_equals(array(array('1'),array('2')),ActiveRecord\wrap_strings_in_arrays($x));
102 |
103 | $x = '1';
104 | $this->assert_equals(array(array('1')),ActiveRecord\wrap_strings_in_arrays($x));
105 | }
106 | };
107 | ?>
108 |
--------------------------------------------------------------------------------
/test/ValidatesFormatOfTest.php:
--------------------------------------------------------------------------------
1 | 1, 'name' => 'testing reg'));
23 | $book->save();
24 | $this->assert_false($book->errors->is_invalid('name'));
25 |
26 | BookFormat::$validates_format_of[0]['with'] = '/[0-9]/';
27 | $book = new BookFormat(array('author_id' => 1, 'name' => 12));
28 | $book->save();
29 | $this->assert_false($book->errors->is_invalid('name'));
30 | }
31 |
32 | public function test_invalid_null()
33 | {
34 | BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
35 | $book = new BookFormat;
36 | $book->name = null;
37 | $book->save();
38 | $this->assert_true($book->errors->is_invalid('name'));
39 | }
40 |
41 | public function test_invalid_blank()
42 | {
43 | BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
44 | $book = new BookFormat;
45 | $book->name = '';
46 | $book->save();
47 | $this->assert_true($book->errors->is_invalid('name'));
48 | }
49 |
50 | public function test_valid_blank_andallow_blank()
51 | {
52 | BookFormat::$validates_format_of[0]['allow_blank'] = true;
53 | BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
54 | $book = new BookFormat(array('author_id' => 1, 'name' => ''));
55 | $book->save();
56 | $this->assert_false($book->errors->is_invalid('name'));
57 | }
58 |
59 | public function test_valid_null_and_allow_null()
60 | {
61 | BookFormat::$validates_format_of[0]['allow_null'] = true;
62 | BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
63 | $book = new BookFormat();
64 | $book->author_id = 1;
65 | $book->name = null;
66 | $book->save();
67 | $this->assert_false($book->errors->is_invalid('name'));
68 | }
69 |
70 | /**
71 | * @expectedException ActiveRecord\ValidationsArgumentError
72 | */
73 | public function test_invalid_lack_of_with_key()
74 | {
75 | $book = new BookFormat;
76 | $book->name = null;
77 | $book->save();
78 | }
79 |
80 | /**
81 | * @expectedException ActiveRecord\ValidationsArgumentError
82 | */
83 | public function test_invalid_with_expression_as_non_string()
84 | {
85 | BookFormat::$validates_format_of[0]['with'] = array('test');
86 | $book = new BookFormat;
87 | $book->name = null;
88 | $book->save();
89 | }
90 |
91 | public function test_invalid_with_expression_as_non_regexp()
92 | {
93 | BookFormat::$validates_format_of[0]['with'] = 'blah';
94 | $book = new BookFormat;
95 | $book->name = 'blah';
96 | $book->save();
97 | $this->assert_true($book->errors->is_invalid('name'));
98 | }
99 |
100 | public function test_custom_message()
101 | {
102 | BookFormat::$validates_format_of[0]['message'] = 'is using a custom message.';
103 | BookFormat::$validates_format_of[0]['with'] = '/[^0-9]/';
104 |
105 | $book = new BookFormat;
106 | $book->name = null;
107 | $book->save();
108 | $this->assert_equals('is using a custom message.', $book->errors->on('name'));
109 | }
110 | };
111 | ?>
--------------------------------------------------------------------------------
/test/ValidatesInclusionAndExclusionOfTest.php:
--------------------------------------------------------------------------------
1 | array('blah', 'alpha', 'bravo'))
8 | );
9 | };
10 |
11 | class BookInclusion extends ActiveRecord\Model
12 | {
13 | static $table = 'books';
14 | public static $validates_inclusion_of = array(
15 | array('name', 'in' => array('blah', 'tanker', 'shark'))
16 | );
17 | };
18 |
19 | class ValidatesInclusionAndExclusionOfTest extends DatabaseTest
20 | {
21 | public function set_up($connection_name=null)
22 | {
23 | parent::set_up($connection_name);
24 | BookInclusion::$validates_inclusion_of[0] = array('name', 'in' => array('blah', 'tanker', 'shark'));
25 | BookExclusion::$validates_exclusion_of[0] = array('name', 'in' => array('blah', 'alpha', 'bravo'));
26 | }
27 |
28 | public function test_inclusion()
29 | {
30 | $book = new BookInclusion;
31 | $book->name = 'blah';
32 | $book->save();
33 | $this->assert_false($book->errors->is_invalid('name'));
34 | }
35 |
36 | public function test_exclusion()
37 | {
38 | $book = new BookExclusion;
39 | $book->name = 'blahh';
40 | $book->save();
41 | $this->assert_false($book->errors->is_invalid('name'));
42 | }
43 |
44 | public function test_invalid_inclusion()
45 | {
46 | $book = new BookInclusion;
47 | $book->name = 'thanker';
48 | $book->save();
49 | $this->assert_true($book->errors->is_invalid('name'));
50 | $book->name = 'alpha ';
51 | $book->save();
52 | $this->assert_true($book->errors->is_invalid('name'));
53 | }
54 |
55 | public function test_invalid_exclusion()
56 | {
57 | $book = new BookExclusion;
58 | $book->name = 'alpha';
59 | $book->save();
60 | $this->assert_true($book->errors->is_invalid('name'));
61 |
62 | $book = new BookExclusion;
63 | $book->name = 'bravo';
64 | $book->save();
65 | $this->assert_true($book->errors->is_invalid('name'));
66 | }
67 |
68 | public function test_inclusion_with_numeric()
69 | {
70 | BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
71 | $book = new BookInclusion;
72 | $book->name = 2;
73 | $book->save();
74 | $this->assert_false($book->errors->is_invalid('name'));
75 | }
76 |
77 | public function test_inclusion_with_boolean()
78 | {
79 | BookInclusion::$validates_inclusion_of[0]['in']= array(true);
80 | $book = new BookInclusion;
81 | $book->name = true;
82 | $book->save();
83 | $this->assert_false($book->errors->is_invalid('name'));
84 | }
85 |
86 | public function test_inclusion_with_null()
87 | {
88 | BookInclusion::$validates_inclusion_of[0]['in']= array(null);
89 | $book = new BookInclusion;
90 | $book->name = null;
91 | $book->save();
92 | $this->assert_false($book->errors->is_invalid('name'));
93 | }
94 |
95 | public function test_invalid_inclusion_with_numeric()
96 | {
97 | BookInclusion::$validates_inclusion_of[0]['in']= array(0, 1, 2);
98 | $book = new BookInclusion;
99 | $book->name = 5;
100 | $book->save();
101 | $this->assert_true($book->errors->is_invalid('name'));
102 | }
103 |
104 | public function tes_inclusion_within_option()
105 | {
106 | BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => array('okay'));
107 | $book = new BookInclusion;
108 | $book->name = 'okay';
109 | $book->save();
110 | $this->assert_false($book->errors->is_invalid('name'));
111 | }
112 |
113 | public function tes_inclusion_scalar_value()
114 | {
115 | BookInclusion::$validates_inclusion_of[0] = array('name', 'within' => 'okay');
116 | $book = new BookInclusion;
117 | $book->name = 'okay';
118 | $book->save();
119 | $this->assert_false($book->errors->is_invalid('name'));
120 | }
121 |
122 | public function test_valid_null()
123 | {
124 | BookInclusion::$validates_inclusion_of[0]['allow_null'] = true;
125 | $book = new BookInclusion;
126 | $book->name = null;
127 | $book->save();
128 | $this->assert_false($book->errors->is_invalid('name'));
129 | }
130 |
131 | public function test_valid_blank()
132 | {
133 | BookInclusion::$validates_inclusion_of[0]['allow_blank'] = true;
134 | $book = new BookInclusion;
135 | $book->name = '';
136 | $book->save();
137 | $this->assert_false($book->errors->is_invalid('name'));
138 | }
139 |
140 | public function test_custom_message()
141 | {
142 | $msg = 'is using a custom message.';
143 | BookInclusion::$validates_inclusion_of[0]['message'] = $msg;
144 | BookExclusion::$validates_exclusion_of[0]['message'] = $msg;
145 |
146 | $book = new BookInclusion;
147 | $book->name = 'not included';
148 | $book->save();
149 | $this->assert_equals('is using a custom message.', $book->errors->on('name'));
150 | $book = new BookExclusion;
151 | $book->name = 'bravo';
152 | $book->save();
153 | $this->assert_equals('is using a custom message.', $book->errors->on('name'));
154 | }
155 |
156 | };
157 | ?>
--------------------------------------------------------------------------------
/test/ValidatesNumericalityOfTest.php:
--------------------------------------------------------------------------------
1 | numeric_test = $value;
34 |
35 | if ($boolean == 'valid')
36 | {
37 | $this->assert_true($book->save());
38 | $this->assert_false($book->errors->is_invalid('numeric_test'));
39 | }
40 | else
41 | {
42 | $this->assert_false($book->save());
43 | $this->assert_true($book->errors->is_invalid('numeric_test'));
44 |
45 | if (!is_null($msg))
46 | $this->assert_same($msg, $book->errors->on('numeric_test'));
47 | }
48 | }
49 |
50 | private function assert_invalid($values, $msg=null)
51 | {
52 | foreach ($values as $value)
53 | $this->assert_validity($value, 'invalid', $msg);
54 | }
55 |
56 | private function assert_valid($values, $msg=null)
57 | {
58 | foreach ($values as $value)
59 | $this->assert_validity($value, 'valid', $msg);
60 | }
61 |
62 | public function test_numericality()
63 | {
64 | //$this->assert_invalid(array("0xdeadbeef"));
65 |
66 | $this->assert_valid(array_merge(self::$FLOATS, self::$INTEGERS));
67 | $this->assert_invalid(array_merge(self::$NULL, self::$BLANK, self::$JUNK));
68 | }
69 |
70 | public function test_not_anumber()
71 | {
72 | $this->assert_invalid(array('blah'), 'is not a number');
73 | }
74 |
75 | public function test_invalid_null()
76 | {
77 | $this->assert_invalid(array(null));
78 | }
79 |
80 | public function test_invalid_blank()
81 | {
82 | $this->assert_invalid(array(' ', ' '), 'is not a number');
83 | }
84 |
85 | public function test_invalid_whitespace()
86 | {
87 | $this->assert_invalid(array(''));
88 | }
89 |
90 | public function test_valid_null()
91 | {
92 | BookNumericality::$validates_numericality_of[0]['allow_null'] = true;
93 | $this->assert_valid(array(null));
94 | }
95 |
96 | public function test_only_integer()
97 | {
98 | BookNumericality::$validates_numericality_of[0]['only_integer'] = true;
99 |
100 | $this->assert_valid(array(1, '1'));
101 | $this->assert_invalid(array(1.5, '1.5'));
102 | }
103 |
104 | public function test_only_integer_matching_does_not_ignore_other_options()
105 | {
106 | BookNumericality::$validates_numericality_of[0]['only_integer'] = true;
107 | BookNumericality::$validates_numericality_of[0]['greater_than'] = 0;
108 |
109 | $this->assert_invalid(array(-1,'-1'));
110 | }
111 |
112 | public function test_greater_than()
113 | {
114 | BookNumericality::$validates_numericality_of[0]['greater_than'] = 5;
115 |
116 | $this->assert_valid(array(6, '7'));
117 | $this->assert_invalid(array(5, '5'), 'must be greater than 5');
118 | }
119 |
120 | public function test_greater_than_or_equal_to()
121 | {
122 | BookNumericality::$validates_numericality_of[0]['greater_than_or_equal_to'] = 5;
123 |
124 | $this->assert_valid(array(5, 5.1, '5.1'));
125 | $this->assert_invalid(array(-50, 4.9, '4.9','-5.1'));
126 | }
127 |
128 | public function test_less_than()
129 | {
130 | BookNumericality::$validates_numericality_of[0]['less_than'] = 5;
131 |
132 | $this->assert_valid(array(4.9, -1, 0, '-5'));
133 | $this->assert_invalid(array(5, '5'), 'must be less than 5');
134 | }
135 |
136 | public function test_less_than_or_equal_to()
137 | {
138 | BookNumericality::$validates_numericality_of[0]['less_than_or_equal_to'] = 5;
139 |
140 | $this->assert_valid(array(5, -1, 0, 4.9, '-5'));
141 | $this->assert_invalid(array('8', 5.1), 'must be less than or equal to 5');
142 | }
143 |
144 | public function test_greater_than_less_than_and_even()
145 | {
146 | BookNumericality::$validates_numericality_of[0] = array('numeric_test', 'greater_than' => 1, 'less_than' => 4, 'even' => true);
147 |
148 | $this->assert_valid(array(2));
149 | $this->assert_invalid(array(1,3,4));
150 | }
151 |
152 | public function test_custom_message()
153 | {
154 | BookNumericality::$validates_numericality_of = array(
155 | array('numeric_test', 'message' => 'Hello')
156 | );
157 | $book = new BookNumericality(array('numeric_test' => 'NaN'));
158 | $book->is_valid();
159 | $this->assert_equals(array('Numeric test Hello'),$book->errors->full_messages());
160 | }
161 | };
162 |
163 | array_merge(ValidatesNumericalityOfTest::$INTEGERS, ValidatesNumericalityOfTest::$INTEGER_STRINGS);
164 | array_merge(ValidatesNumericalityOfTest::$FLOATS, ValidatesNumericalityOfTest::$FLOAT_STRINGS);
165 | ?>
166 |
--------------------------------------------------------------------------------
/test/ValidatesPresenceOfTest.php:
--------------------------------------------------------------------------------
1 | 'blah'));
26 | $this->assert_false($book->is_invalid());
27 | }
28 |
29 | public function test_presence_on_date_field_is_valid()
30 | {
31 | $author = new AuthorPresence(array('some_date' => '2010-01-01'));
32 | $this->assert_true($author->is_valid());
33 | }
34 |
35 | public function test_presence_on_date_field_is_not_valid()
36 | {
37 | $author = new AuthorPresence();
38 | $this->assert_false($author->is_valid());
39 | }
40 |
41 | public function test_invalid_null()
42 | {
43 | $book = new BookPresence(array('name' => null));
44 | $this->assert_true($book->is_invalid());
45 | }
46 |
47 | public function test_invalid_blank()
48 | {
49 | $book = new BookPresence(array('name' => ''));
50 | $this->assert_true($book->is_invalid());
51 | }
52 |
53 | public function test_valid_white_space()
54 | {
55 | $book = new BookPresence(array('name' => ' '));
56 | $this->assert_false($book->is_invalid());
57 | }
58 |
59 | public function test_custom_message()
60 | {
61 | BookPresence::$validates_presence_of[0]['message'] = 'is using a custom message.';
62 |
63 | $book = new BookPresence(array('name' => null));
64 | $book->is_valid();
65 | $this->assert_equals('is using a custom message.', $book->errors->on('name'));
66 | }
67 |
68 | public function test_valid_zero()
69 | {
70 | $book = new BookPresence(array('name' => 0));
71 | $this->assert_true($book->is_valid());
72 | }
73 | };
74 | ?>
--------------------------------------------------------------------------------
/test/ValidationsTest.php:
--------------------------------------------------------------------------------
1 | 'name', 'x' => 'secondary_author_id');
9 | static $validates_presence_of = array();
10 | static $validates_uniqueness_of = array();
11 | static $custom_validator_error_msg = 'failed custom validation';
12 |
13 | // fired for every validation - but only used for custom validation test
14 | public function validate()
15 | {
16 | if ($this->name == 'test_custom_validation')
17 | $this->errors->add('name', self::$custom_validator_error_msg);
18 | }
19 | }
20 |
21 | class ValuestoreValidations extends ActiveRecord\Model
22 | {
23 | static $table_name = 'valuestore';
24 | static $validates_uniqueness_of = array();
25 | }
26 |
27 | class ValidationsTest extends DatabaseTest
28 | {
29 | public function set_up($connection_name=null)
30 | {
31 | parent::set_up($connection_name);
32 |
33 | BookValidations::$validates_presence_of[0] = 'name';
34 | BookValidations::$validates_uniqueness_of[0] = 'name';
35 |
36 | ValuestoreValidations::$validates_uniqueness_of[0] = 'key';
37 | }
38 |
39 | public function test_is_valid_invokes_validations()
40 | {
41 | $book = new Book;
42 | $this->assert_true(empty($book->errors));
43 | $book->is_valid();
44 | $this->assert_false(empty($book->errors));
45 | }
46 |
47 | public function test_is_valid_returns_true_if_no_validations_exist()
48 | {
49 | $book = new Book;
50 | $this->assert_true($book->is_valid());
51 | }
52 |
53 | public function test_is_valid_returns_false_if_failed_validations()
54 | {
55 | $book = new BookValidations;
56 | $this->assert_false($book->is_valid());
57 | }
58 |
59 | public function test_is_invalid()
60 | {
61 | $book = new Book();
62 | $this->assert_false($book->is_invalid());
63 | }
64 |
65 | public function test_is_invalid_is_true()
66 | {
67 | $book = new BookValidations();
68 | $this->assert_true($book->is_invalid());
69 | }
70 |
71 | public function test_is_iterable()
72 | {
73 | $book = new BookValidations();
74 | $book->is_valid();
75 |
76 | foreach ($book->errors as $name => $message)
77 | $this->assert_equals("Name can't be blank",$message);
78 | }
79 |
80 | public function test_full_messages()
81 | {
82 | $book = new BookValidations();
83 | $book->is_valid();
84 |
85 | $this->assert_equals(array("Name can't be blank"),array_values($book->errors->full_messages(array('hash' => true))));
86 | }
87 |
88 | public function test_to_array()
89 | {
90 | $book = new BookValidations();
91 | $book->is_valid();
92 |
93 | $this->assert_equals(array("name" => array("Name can't be blank")), $book->errors->to_array());
94 | }
95 |
96 | public function test_toString()
97 | {
98 | $book = new BookValidations();
99 | $book->is_valid();
100 | $book->errors->add('secondary_author_id', "is invalid");
101 |
102 | $this->assert_equals("Name can't be blank\nSecondary author id is invalid", (string) $book->errors);
103 | }
104 |
105 | public function test_validates_uniqueness_of()
106 | {
107 | BookValidations::create(array('name' => 'bob'));
108 | $book = BookValidations::create(array('name' => 'bob'));
109 |
110 | $this->assert_equals(array("Name must be unique"),$book->errors->full_messages());
111 | $this->assert_equals(1,BookValidations::count(array('conditions' => "name='bob'")));
112 | }
113 |
114 | public function test_validates_uniqueness_of_excludes_self()
115 | {
116 | $book = BookValidations::first();
117 | $this->assert_equals(true,$book->is_valid());
118 | }
119 |
120 | public function test_validates_uniqueness_of_with_multiple_fields()
121 | {
122 | BookValidations::$validates_uniqueness_of[0] = array(array('name','special'));
123 | $book1 = BookValidations::first();
124 | $book2 = new BookValidations(array('name' => $book1->name, 'special' => $book1->special+1));
125 | $this->assert_true($book2->is_valid());
126 | }
127 |
128 | public function test_validates_uniqueness_of_with_multiple_fields_is_not_unique()
129 | {
130 | BookValidations::$validates_uniqueness_of[0] = array(array('name','special'));
131 | $book1 = BookValidations::first();
132 | $book2 = new BookValidations(array('name' => $book1->name, 'special' => $book1->special));
133 | $this->assert_false($book2->is_valid());
134 | $this->assert_equals(array('Name and special must be unique'),$book2->errors->full_messages());
135 | }
136 |
137 | public function test_validates_uniqueness_of_works_with_alias_attribute()
138 | {
139 | BookValidations::$validates_uniqueness_of[0] = array(array('name_alias','x'));
140 | $book = BookValidations::create(array('name_alias' => 'Another Book', 'x' => 2));
141 | $this->assert_false($book->is_valid());
142 | $this->assert_equals(array('Name alias and x must be unique'), $book->errors->full_messages());
143 | }
144 |
145 | public function test_validates_uniqueness_of_works_with_mysql_reserved_word_as_column_name()
146 | {
147 | ValuestoreValidations::create(array('key' => 'GA_KEY', 'value' => 'UA-1234567-1'));
148 | $valuestore = ValuestoreValidations::create(array('key' => 'GA_KEY', 'value' => 'UA-1234567-2'));
149 |
150 | $this->assert_equals(array("Key must be unique"),$valuestore->errors->full_messages());
151 | $this->assert_equals(1,ValuestoreValidations::count(array('conditions' => "`key`='GA_KEY'")));
152 | }
153 |
154 | public function test_get_validation_rules()
155 | {
156 | $validators = BookValidations::first()->get_validation_rules();
157 | $this->assert_true(in_array(array('validator' => 'validates_presence_of'),$validators['name']));
158 | }
159 |
160 | public function test_model_is_nulled_out_to_prevent_memory_leak()
161 | {
162 | $book = new BookValidations();
163 | $book->is_valid();
164 | $this->assert_true(strpos(serialize($book->errors),'model";N;') !== false);
165 | }
166 |
167 | public function test_validations_takes_strings()
168 | {
169 | BookValidations::$validates_presence_of = array('numeric_test', array('special'), 'name');
170 | $book = new BookValidations(array('numeric_test' => 1, 'special' => 1));
171 | $this->assert_false($book->is_valid());
172 | }
173 |
174 | public function test_gh131_custom_validation()
175 | {
176 | $book = new BookValidations(array('name' => 'test_custom_validation'));
177 | $book->save();
178 | $this->assert_true($book->errors->is_invalid('name'));
179 | $this->assert_equals(BookValidations::$custom_validator_error_msg, $book->errors->on('name'));
180 | }
181 | };
182 | ?>
183 |
--------------------------------------------------------------------------------
/test/fixtures/amenities.csv:
--------------------------------------------------------------------------------
1 | amenity_id, type
2 | 1, "Test #1"
3 | 2, "Test #2"
4 | 3, "Test #3"
--------------------------------------------------------------------------------
/test/fixtures/authors.csv:
--------------------------------------------------------------------------------
1 | author_id,parent_author_id,name,publisher_id
2 | 1,3,"Tito",1
3 | 2,2,"George W. Bush",1
4 | 3,1,"Bill Clinton",2
5 | 4,2,"Uncle Bob",3
--------------------------------------------------------------------------------
/test/fixtures/awesome_people.csv:
--------------------------------------------------------------------------------
1 | id,author_id
2 | 1,1
3 | 2,2
4 | 3,3
--------------------------------------------------------------------------------
/test/fixtures/books.csv:
--------------------------------------------------------------------------------
1 | book_id,author_id,secondary_author_id,name,special
2 | 1,1,2,"Ancient Art of Main Tanking",0
3 | 2,2,2,"Another Book",0
--------------------------------------------------------------------------------
/test/fixtures/employees.csv:
--------------------------------------------------------------------------------
1 | id,first_name,last_name,nick_name
2 | 1,"michio","kaku","kakz"
3 | 2,"jacques","fuentes","jax"
4 | 3,"kien","la","kla"
--------------------------------------------------------------------------------
/test/fixtures/events.csv:
--------------------------------------------------------------------------------
1 | id,venue_id,host_id,title,description,type
2 | 1,1,1,"Monday Night Music Club feat. The Shivers","","Music"
3 | 2,2,2,"Yeah Yeah Yeahs","","Music"
4 | 3,2,3,"Love Overboard","","Music"
5 | 5,6,4,"1320 Records Presents A \"Live PA Set\" By STS9 with",,"Music"
6 | 6,500,4,"Kla likes to dance to YMCA","","Music"
7 | 7,9,4,"Blah",,"Blah"
--------------------------------------------------------------------------------
/test/fixtures/hosts.csv:
--------------------------------------------------------------------------------
1 | id,name
2 | 1,"David Letterman"
3 | 2,"Billy Crystal"
4 | 3,"Jon Stewart"
5 | 4,"Funny Guy"
--------------------------------------------------------------------------------
/test/fixtures/newsletters.csv:
--------------------------------------------------------------------------------
1 | id
2 | 1
3 |
--------------------------------------------------------------------------------
/test/fixtures/positions.csv:
--------------------------------------------------------------------------------
1 | id,employee_id,title,active
2 | 3,1,"physicist",0
3 | 2,2,"programmer",1
4 | 1,3,"programmer",1
--------------------------------------------------------------------------------
/test/fixtures/property.csv:
--------------------------------------------------------------------------------
1 | property_id
2 | 28840
3 | 28841
--------------------------------------------------------------------------------
/test/fixtures/property_amenities.csv:
--------------------------------------------------------------------------------
1 | id, amenity_id, property_id
2 | 257117, 1, 28840
3 | 257118, 2, 28840
4 | 257119, 2, 28841
5 | 257120, 3, 28841
6 |
--------------------------------------------------------------------------------
/test/fixtures/publishers.csv:
--------------------------------------------------------------------------------
1 | publisher_id,name
2 | 1,"Random House"
3 | 2,"Houghton Mifflin"
4 | 3,"Scholastic"
5 |
--------------------------------------------------------------------------------
/test/fixtures/rm-bldg.csv:
--------------------------------------------------------------------------------
1 | rm-id,rm-name,"space out"
2 | 1,"name","x"
--------------------------------------------------------------------------------
/test/fixtures/user_newsletters.csv:
--------------------------------------------------------------------------------
1 | id,user_id,newsletter_id
2 | 1,1,1
3 |
--------------------------------------------------------------------------------
/test/fixtures/users.csv:
--------------------------------------------------------------------------------
1 | id
2 | 1
3 |
--------------------------------------------------------------------------------
/test/fixtures/valuestore.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpfuentes2/php-activerecord/d9a4130adbef8f5446cbdcc96b42b8204e3da5b4/test/fixtures/valuestore.csv
--------------------------------------------------------------------------------
/test/fixtures/venues.csv:
--------------------------------------------------------------------------------
1 | id,name,city,state,address,phone
2 | 1,"Blender Theater at Gramercy","New York","NY","127 East 23rd Street","2127776800"
3 | 2,"Warner Theatre","Washington","DC","1299 Pennsylvania Ave NW","2027834000"
4 | 6,"The Note - West Chester","West Chester","PA","142 E. Market St.","0000000000"
5 | 7,"The National","Richmond","VA","708 East Broad Street","1112223333"
6 | 8,"Hampton Coliseum","Hampton","VA","1000 Coliseum Dr","2223334444"
7 | 9,"YMCA","Washington","DC","1234 YMCA Way","2222222222"
--------------------------------------------------------------------------------
/test/helpers/DatabaseLoader.php:
--------------------------------------------------------------------------------
1 | db = $db;
10 |
11 | if (!isset(static::$instances[$db->protocol]))
12 | static::$instances[$db->protocol] = 0;
13 |
14 | if (static::$instances[$db->protocol]++ == 0)
15 | {
16 | // drop and re-create the tables one time only
17 | $this->drop_tables();
18 | $this->exec_sql_script($db->protocol);
19 | }
20 | }
21 |
22 | public function reset_table_data()
23 | {
24 | foreach ($this->get_fixture_tables() as $table)
25 | {
26 | if ($this->db->protocol == 'oci' && $table == 'rm-bldg')
27 | continue;
28 |
29 | $this->db->query('DELETE FROM ' . $this->quote_name($table));
30 | $this->load_fixture_data($table);
31 | }
32 |
33 | $after_fixtures = $this->db->protocol.'-after-fixtures';
34 | try {
35 | $this->exec_sql_script($after_fixtures);
36 | } catch (Exception $e) {
37 | // pass
38 | }
39 | }
40 |
41 | public function drop_tables()
42 | {
43 | $tables = $this->db->tables();
44 |
45 | foreach ($this->get_fixture_tables() as $table)
46 | {
47 | if ($this->db->protocol == 'oci')
48 | {
49 | $table = strtoupper($table);
50 |
51 | if ($table == 'RM-BLDG')
52 | continue;
53 | }
54 |
55 | if (in_array($table,$tables))
56 | $this->db->query('DROP TABLE ' . $this->quote_name($table));
57 |
58 | if ($this->db->protocol == 'oci')
59 | {
60 | try {
61 | $this->db->query("DROP SEQUENCE {$table}_seq");
62 | } catch (ActiveRecord\DatabaseException $e) {
63 | // ignore
64 | }
65 | }
66 | }
67 | }
68 |
69 | public function exec_sql_script($file)
70 | {
71 | foreach (explode(';',$this->get_sql($file)) as $sql)
72 | {
73 | if (trim($sql) != '')
74 | $this->db->query($sql);
75 | }
76 | }
77 |
78 | public function get_fixture_tables()
79 | {
80 | $tables = array();
81 |
82 | foreach (glob(__DIR__ . '/../fixtures/*.csv') as $file)
83 | {
84 | $info = pathinfo($file);
85 | $tables[] = $info['filename'];
86 | }
87 |
88 | return $tables;
89 | }
90 |
91 | public function get_sql($file)
92 | {
93 | $file = __DIR__ . "/../sql/$file.sql";
94 |
95 | if (!file_exists($file))
96 | throw new Exception("File not found: $file");
97 |
98 | return file_get_contents($file);
99 | }
100 |
101 | public function load_fixture_data($table)
102 | {
103 | $fp = fopen(__DIR__ . "/../fixtures/$table.csv",'r');
104 | $fields = fgetcsv($fp);
105 |
106 | if (!empty($fields))
107 | {
108 | $markers = join(',',array_fill(0,count($fields),'?'));
109 | $table = $this->quote_name($table);
110 |
111 | foreach ($fields as &$name)
112 | $name = $this->quote_name(trim($name));
113 |
114 | $fields = join(',',$fields);
115 |
116 | while (($values = fgetcsv($fp)))
117 | $this->db->query("INSERT INTO $table($fields) VALUES($markers)",$values);
118 | }
119 | fclose($fp);
120 | }
121 |
122 | public function quote_name($name)
123 | {
124 | if ($this->db->protocol == 'oci')
125 | $name = strtoupper($name);
126 |
127 | return $this->db->quote_name($name);
128 | }
129 | }
130 | ?>
131 |
--------------------------------------------------------------------------------
/test/helpers/DatabaseTest.php:
--------------------------------------------------------------------------------
1 | original_default_connection = $config->get_default_connection();
16 |
17 | $this->original_date_class = $config->get_date_class();
18 |
19 | if ($connection_name)
20 | $config->set_default_connection($connection_name);
21 |
22 | if ($connection_name == 'sqlite' || $config->get_default_connection() == 'sqlite')
23 | {
24 | // need to create the db. the adapter specifically does not create it for us.
25 | static::$db = substr(ActiveRecord\Config::instance()->get_connection('sqlite'),9);
26 | new SQLite3(static::$db);
27 | }
28 |
29 | $this->connection_name = $connection_name;
30 | try {
31 | $this->conn = ActiveRecord\ConnectionManager::get_connection($connection_name);
32 | } catch (ActiveRecord\DatabaseException $e) {
33 | $this->mark_test_skipped($connection_name . ' failed to connect. '.$e->getMessage());
34 | }
35 |
36 | $GLOBALS['ACTIVERECORD_LOG'] = false;
37 |
38 | $loader = new DatabaseLoader($this->conn);
39 | $loader->reset_table_data();
40 |
41 | if (self::$log)
42 | $GLOBALS['ACTIVERECORD_LOG'] = true;
43 | }
44 |
45 | public function tear_down()
46 | {
47 | ActiveRecord\Config::instance()->set_date_class($this->original_date_class);
48 | if ($this->original_default_connection)
49 | ActiveRecord\Config::instance()->set_default_connection($this->original_default_connection);
50 | }
51 |
52 | public function assert_exception_message_contains($contains, $closure)
53 | {
54 | $message = "";
55 |
56 | try {
57 | $closure();
58 | } catch (ActiveRecord\UndefinedPropertyException $e) {
59 | $message = $e->getMessage();
60 | }
61 |
62 | $this->assertContains($contains, $message);
63 | }
64 |
65 | /**
66 | * Returns true if $regex matches $actual.
67 | *
68 | * Takes database specific quotes into account by removing them. So, this won't
69 | * work if you have actual quotes in your strings.
70 | */
71 | public function assert_sql_has($needle, $haystack)
72 | {
73 | $needle = str_replace(array('"','`'),'',$needle);
74 | $haystack = str_replace(array('"','`'),'',$haystack);
75 | return $this->assertContains($needle, $haystack);
76 | }
77 |
78 | public function assert_sql_doesnt_has($needle, $haystack)
79 | {
80 | $needle = str_replace(array('"','`'),'',$needle);
81 | $haystack = str_replace(array('"','`'),'',$haystack);
82 | return $this->assertNotContains($needle, $haystack);
83 | }
84 | }
85 | ?>
86 |
--------------------------------------------------------------------------------
/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php:
--------------------------------------------------------------------------------
1 | camelize($meth);
7 |
8 | if (method_exists($this, $camel_cased_method))
9 | return call_user_func_array(array($this, $camel_cased_method), $args);
10 |
11 | $class_name = get_called_class();
12 | $trace = debug_backtrace();
13 | die("PHP Fatal Error: Call to undefined method $class_name::$meth() in {$trace[1]['file']} on line {$trace[1]['line']}" . PHP_EOL);
14 | }
15 |
16 | public function setUp()
17 | {
18 | if (method_exists($this,'set_up'))
19 | call_user_func_array(array($this,'set_up'),func_get_args());
20 | }
21 |
22 | public function tearDown()
23 | {
24 | if (method_exists($this,'tear_down'))
25 | call_user_func_array(array($this,'tear_down'),func_get_args());
26 | }
27 |
28 | private function setup_assert_keys($args)
29 | {
30 | $last = count($args)-1;
31 | $keys = array_slice($args,0,$last);
32 | $array = $args[$last];
33 | return array($keys,$array);
34 | }
35 |
36 | public function assert_has_keys(/* $keys..., $array */)
37 | {
38 | list($keys,$array) = $this->setup_assert_keys(func_get_args());
39 |
40 | $this->assert_not_null($array,'Array was null');
41 |
42 | foreach ($keys as $name)
43 | $this->assert_array_has_key($name,$array);
44 | }
45 |
46 | public function assert_doesnt_has_keys(/* $keys..., $array */)
47 | {
48 | list($keys,$array) = $this->setup_assert_keys(func_get_args());
49 |
50 | foreach ($keys as $name)
51 | $this->assert_array_not_has_key($name,$array);
52 | }
53 |
54 | public function assert_is_a($expected_class, $object)
55 | {
56 | $this->assert_equals($expected_class,get_class($object));
57 | }
58 |
59 | public function assert_datetime_equals($expected, $actual)
60 | {
61 | $this->assert_equals($expected->format(DateTime::ISO8601),$actual->format(DateTime::ISO8601));
62 | }
63 | }
64 | ?>
--------------------------------------------------------------------------------
/test/helpers/config.php:
--------------------------------------------------------------------------------
1 | set_model_directory(realpath(__DIR__ . '/../models'));
42 | $cfg->set_connections(array(
43 | 'mysql' => getenv('PHPAR_MYSQL') ?: 'mysql://test:test@127.0.0.1/test',
44 | 'pgsql' => getenv('PHPAR_PGSQL') ?: 'pgsql://test:test@127.0.0.1/test',
45 | 'oci' => getenv('PHPAR_OCI') ?: 'oci://test:test@127.0.0.1/dev',
46 | 'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db'));
47 |
48 | $cfg->set_default_connection('mysql');
49 |
50 | for ($i=0; $iset_default_connection($GLOBALS['argv'][$i+1]);
54 | elseif ($GLOBALS['argv'][$i] == '--slow-tests')
55 | $GLOBALS['slow_tests'] = true;
56 | }
57 |
58 | if (class_exists('Log_file')) // PEAR Log installed
59 | {
60 | $logger = new Log_file(dirname(__FILE__) . '/../log/query.log','ident',array('mode' => 0664, 'timeFormat' => '%Y-%m-%d %H:%M:%S'));
61 |
62 | $cfg->set_logging(true);
63 | $cfg->set_logger($logger);
64 | }
65 | else
66 | {
67 | if ($GLOBALS['show_warnings'] && !isset($GLOBALS['show_warnings_done']))
68 | echo "(Logging SQL queries disabled, PEAR::Log not found.)\n";
69 |
70 | DatabaseTest::$log = false;
71 | }
72 |
73 | if ($GLOBALS['show_warnings'] && !isset($GLOBALS['show_warnings_done']))
74 | {
75 | if (!extension_loaded('memcache'))
76 | echo "(Cache Tests will be skipped, Memcache not found.)\n";
77 | }
78 |
79 | date_default_timezone_set('UTC');
80 |
81 | $GLOBALS['show_warnings_done'] = true;
82 | });
83 |
84 | error_reporting(E_ALL | E_STRICT);
85 | ?>
--------------------------------------------------------------------------------
/test/helpers/foo.php:
--------------------------------------------------------------------------------
1 | 'user_newsletters')
9 | );
10 |
11 | }
12 |
13 | class Newsletter extends \ActiveRecord\Model {
14 | static $has_many = array(
15 | array('user_newsletters'),
16 | array('users', 'through' => 'user_newsletters'),
17 | );
18 | }
19 |
20 | class UserNewsletter extends \ActiveRecord\Model {
21 | static $belong_to = array(
22 | array('user'),
23 | array('newsletter'),
24 | );
25 | }
26 |
27 | # vim: ts=4 noet nobinary
28 | ?>
29 |
--------------------------------------------------------------------------------
/test/models/Amenity.php:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/test/models/Author.php:
--------------------------------------------------------------------------------
1 | 'author_id', 'primary_key' => 'author_id'),
6 | // array('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'));
7 | static $has_many = array('books');
8 | static $has_one = array(
9 | array('awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'),
10 | array('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'));
11 | static $belongs_to = array();
12 |
13 | public function set_password($plaintext)
14 | {
15 | $this->encrypted_password = md5($plaintext);
16 | }
17 |
18 | public function set_name($value)
19 | {
20 | $value = strtoupper($value);
21 | $this->assign_attribute('name',$value);
22 | }
23 |
24 | public function return_something()
25 | {
26 | return array("sharks" => "lasers");
27 | }
28 | };
29 | ?>
30 |
--------------------------------------------------------------------------------
/test/models/AuthorAttrAccessible.php:
--------------------------------------------------------------------------------
1 | 'BookAttrProtected', 'foreign_key' => 'author_id', 'primary_key' => 'book_id')
8 | );
9 | static $has_one = array(
10 | array('parent_author', 'class_name' => 'AuthorAttrAccessible', 'foreign_key' => 'parent_author_id', 'primary_key' => 'author_id')
11 | );
12 | static $belongs_to = array();
13 |
14 | // No attributes should be accessible
15 | static $attr_accessible = array(null);
16 | };
17 | ?>
18 |
--------------------------------------------------------------------------------
/test/models/AwesomePerson.php:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/test/models/Book.php:
--------------------------------------------------------------------------------
1 | name);
11 | }
12 |
13 | public function name()
14 | {
15 | return strtolower($this->name);
16 | }
17 |
18 | public function get_name()
19 | {
20 | if (self::$use_custom_get_name_getter)
21 | return strtoupper($this->read_attribute('name'));
22 | else
23 | return $this->read_attribute('name');
24 | }
25 |
26 | public function get_upper_name()
27 | {
28 | return strtoupper($this->name);
29 | }
30 |
31 | public function get_lower_name()
32 | {
33 | return strtolower($this->name);
34 | }
35 | };
36 | ?>
37 |
--------------------------------------------------------------------------------
/test/models/BookAttrAccessible.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/BookAttrProtected.php:
--------------------------------------------------------------------------------
1 | 'AuthorAttrAccessible', 'primary_key' => 'author_id')
8 | );
9 |
10 | // No attributes should be accessible
11 | static $attr_accessible = array(null);
12 | };
13 | ?>
14 |
--------------------------------------------------------------------------------
/test/models/Employee.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/Event.php:
--------------------------------------------------------------------------------
1 | 'venue'),
11 | array('name', 'to' => 'host', 'prefix' => 'woot')
12 | );
13 | };
14 | ?>
15 |
--------------------------------------------------------------------------------
/test/models/Host.php:
--------------------------------------------------------------------------------
1 | 'events')
7 | );
8 | }
9 | ?>
10 |
--------------------------------------------------------------------------------
/test/models/JoinAuthor.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/JoinBook.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/NamespaceTest/Book.php:
--------------------------------------------------------------------------------
1 | '\NamespaceTest\Book'),
8 | array('parent_book_2', 'class_name' => 'Book'),
9 | array('parent_book_3', 'class_name' => '\Book'),
10 | );
11 |
12 | static $has_many = array(
13 | array('pages', 'class_name' => '\NamespaceTest\SubNamespaceTest\Page'),
14 | array('pages_2', 'class_name' => 'SubNamespaceTest\Page'),
15 | );
16 | }
17 | ?>
18 |
--------------------------------------------------------------------------------
/test/models/NamespaceTest/SubNamespaceTest/Page.php:
--------------------------------------------------------------------------------
1 | '\NamespaceTest\Book'),
8 | );
9 | }
10 | ?>
11 |
--------------------------------------------------------------------------------
/test/models/Position.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/Property.php:
--------------------------------------------------------------------------------
1 | 'property_amenities')
10 | );
11 | };
12 | ?>
13 |
--------------------------------------------------------------------------------
/test/models/PropertyAmenity.php:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/test/models/Publisher.php:
--------------------------------------------------------------------------------
1 | 'is missing!@#'),
8 | array('rm_name')
9 | );
10 |
11 | static $validates_length_of = array(
12 | array('space_out', 'within' => array(1, 5)),
13 | array('space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long')
14 | );
15 |
16 | static $validates_inclusion_of = array(
17 | array('space_out', 'in' => array('jpg', 'gif', 'png'), 'message' => 'extension %s is not included in the list'),
18 | );
19 |
20 | static $validates_exclusion_of = array(
21 | array('space_out', 'in' => array('jpeg'))
22 | );
23 |
24 | static $validates_format_of = array(
25 | array('space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i' )
26 | );
27 |
28 | static $validates_numericality_of = array(
29 | array('space_out', 'less_than' => 9, 'greater_than' => '5'),
30 | array('rm_id', 'less_than' => 10, 'odd' => null)
31 | );
32 | }
33 | ?>
34 |
--------------------------------------------------------------------------------
/test/models/Venue.php:
--------------------------------------------------------------------------------
1 | 'events')
11 | );
12 |
13 | static $has_one;
14 |
15 | static $alias_attribute = array(
16 | 'marquee' => 'name',
17 | 'mycity' => 'city'
18 | );
19 |
20 | public function get_state()
21 | {
22 | if (self::$use_custom_get_state_getter)
23 | return strtolower($this->read_attribute('state'));
24 | else
25 | return $this->read_attribute('state');
26 | }
27 |
28 | public function set_state($value)
29 | {
30 | if (self::$use_custom_set_state_setter)
31 | return $this->assign_attribute('state', $value . '#');
32 | else
33 | return $this->assign_attribute('state', $value);
34 | }
35 |
36 | };
37 | ?>
38 |
--------------------------------------------------------------------------------
/test/models/VenueAfterCreate.php:
--------------------------------------------------------------------------------
1 | name == 'change me')
11 | {
12 | $this->name = 'changed!';
13 | $this->save();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/models/VenueCB.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/sql/mysql.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE authors(
2 | author_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
3 | parent_author_id INT,
4 | publisher_id INT,
5 | name VARCHAR(25) NOT NULL DEFAULT 'default_name',
6 | updated_at datetime,
7 | created_at datetime,
8 | some_Date date,
9 | some_time time,
10 | some_text text,
11 | some_enum enum('a','b','c'),
12 | encrypted_password varchar(50),
13 | mixedCaseField varchar(50)
14 | ) ENGINE=InnoDB;
15 |
16 | CREATE TABLE books(
17 | book_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
18 | Author_Id INT,
19 | secondary_author_id INT,
20 | name VARCHAR(50),
21 | numeric_test VARCHAR(10) DEFAULT '0',
22 | special NUMERIC(10,2) DEFAULT 0
23 | );
24 |
25 | CREATE TABLE publishers(
26 | publisher_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
27 | name VARCHAR(25) NOT NULL DEFAULT 'default_name'
28 | ) ENGINE=InnoDB;
29 |
30 | CREATE TABLE venues (
31 | Id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
32 | name varchar(50),
33 | city varchar(60),
34 | state char(2),
35 | address varchar(50),
36 | phone varchar(10) default NULL,
37 | UNIQUE(name,address)
38 | );
39 |
40 | CREATE TABLE events (
41 | id int NOT NULL auto_increment PRIMARY KEY,
42 | venue_id int NULL,
43 | host_id int NOT NULL,
44 | title varchar(60) NOT NULL,
45 | description varchar(50),
46 | type varchar(15) default NULL
47 | );
48 |
49 | CREATE TABLE hosts(
50 | id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
51 | name VARCHAR(25)
52 | );
53 |
54 | CREATE TABLE employees (
55 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
56 | first_name VARCHAR(255) NOT NULL,
57 | last_name VARCHAR(255) NOT NULL,
58 | nick_name VARCHAR(255) NOT NULL
59 | );
60 |
61 | CREATE TABLE positions (
62 | id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
63 | employee_id int NOT NULL,
64 | title VARCHAR(255) NOT NULL,
65 | active SMALLINT NOT NULL
66 | );
67 |
68 | CREATE TABLE `rm-bldg`(
69 | `rm-id` INT NOT NULL,
70 | `rm-name` VARCHAR(10) NOT NULL,
71 | `space out` VARCHAR(1) NOT NULL
72 | );
73 |
74 | CREATE TABLE awesome_people(
75 | id int not null primary key auto_increment,
76 | author_id int,
77 | is_awesome int default 1
78 | );
79 |
80 | CREATE TABLE amenities(
81 | `amenity_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
82 | `type` varchar(40) NOT NULL DEFAULT ''
83 | );
84 |
85 | CREATE TABLE property(
86 | `property_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
87 | );
88 |
89 | CREATE TABLE property_amenities(
90 | `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
91 | `amenity_id` int(11) NOT NULL DEFAULT '0',
92 | `property_id` int(11) NOT NULL DEFAULT '0'
93 | );
94 |
95 | CREATE TABLE users (
96 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
97 | ) ENGINE=InnoDB;
98 |
99 | CREATE TABLE newsletters (
100 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
101 | ) ENGINE=InnoDB;
102 |
103 | CREATE TABLE user_newsletters (
104 | id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
105 | user_id INT NOT NULL,
106 | newsletter_id INT NOT NULL
107 | ) ENGINE=InnoDB;
108 |
109 | CREATE TABLE valuestore (
110 | `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
111 | `key` varchar(20) NOT NULL DEFAULT '',
112 | `value` varchar(255) NOT NULL DEFAULT ''
113 | ) ENGINE=InnoDB;
--------------------------------------------------------------------------------
/test/sql/oci-after-fixtures.sql:
--------------------------------------------------------------------------------
1 | DROP SEQUENCE authors_seq;
2 | CREATE SEQUENCE authors_seq START WITH 100;
3 |
4 | DROP SEQUENCE books_seq;
5 | CREATE SEQUENCE books_seq START WITH 100;
6 |
7 | DROP SEQUENCE publishers_seq;
8 | CREATE SEQUENCE publishers_seq START WITH 100;
9 |
10 | DROP SEQUENCE venues_seq;
11 | CREATE SEQUENCE venues_seq START WITH 100;
12 |
13 | DROP SEQUENCE events_seq;
14 | CREATE SEQUENCE events_seq START WITH 100;
15 |
16 | DROP SEQUENCE hosts_seq;
17 | CREATE SEQUENCE hosts_seq START WITH 100;
18 |
19 | DROP SEQUENCE employees_seq;
20 | CREATE SEQUENCE employees_seq START WITH 100;
21 |
22 | DROP SEQUENCE positions_seq;
23 | CREATE SEQUENCE positions_seq START WITH 100;
24 |
25 | DROP SEQUENCE awesome_people_seq;
26 | CREATE SEQUENCE awesome_people_seq START WITH 100;
27 |
28 | DROP SEQUENCE amenities_seq;
29 | CREATE SEQUENCE amenities_seq START WITH 100;
30 |
31 | DROP SEQUENCE property_seq;
32 | CREATE SEQUENCE property_seq START WITH 100;
33 |
34 | DROP SEQUENCE property_amenities_seq;
35 | CREATE SEQUENCE property_amenities_seq START WITH 100;
36 |
37 | DROP SEQUENCE valuestore_seq;
38 | CREATE SEQUENCE valuestore_seq START WITH 100;
39 |
--------------------------------------------------------------------------------
/test/sql/oci.sql:
--------------------------------------------------------------------------------
1 | CREATE SEQUENCE authors_seq;
2 | CREATE TABLE authors(
3 | author_id INT NOT NULL PRIMARY KEY,
4 | parent_author_id INT,
5 | publisher_id INT,
6 | name VARCHAR(25) DEFAULT 'default_name' NOT NULL,
7 | updated_at timestamp,
8 | created_at timestamp,
9 | some_date date,
10 | --some_time time,
11 | some_text varchar2(100),
12 | encrypted_password varchar(50),
13 | "mixedCaseField" varchar(50)
14 | );
15 |
16 | CREATE SEQUENCE books_seq;
17 | CREATE TABLE books(
18 | book_id INT NOT NULL PRIMARY KEY,
19 | Author_Id INT,
20 | secondary_author_id INT,
21 | name VARCHAR(50),
22 | numeric_test VARCHAR(10) DEFAULT '0',
23 | special NUMERIC(10,2) DEFAULT 0);
24 |
25 | CREATE SEQUENCE publishers_seq;
26 | CREATE TABLE publishers(
27 | publisher_id INT NOT NULL PRIMARY KEY,
28 | name VARCHAR(25) DEFAULT 'default_name' NOT NULL,
29 | );
30 |
31 | CREATE SEQUENCE venues_seq;
32 | CREATE TABLE venues (
33 | Id INT NOT NULL PRIMARY KEY,
34 | name varchar(50),
35 | city varchar(60),
36 | state char(2),
37 | address varchar(50),
38 | phone varchar(10) default NULL,
39 | UNIQUE(name,address)
40 | );
41 |
42 | CREATE SEQUENCE events_seq;
43 | CREATE TABLE events (
44 | id INT NOT NULL PRIMARY KEY,
45 | venue_id int NULL,
46 | host_id int NOT NULL,
47 | title varchar(60) NOT NULL,
48 | description varchar(10),
49 | type varchar(15) default NULL
50 | );
51 |
52 | CREATE SEQUENCE hosts_seq;
53 | CREATE TABLE hosts(
54 | id INT NOT NULL PRIMARY KEY,
55 | name VARCHAR(25)
56 | );
57 |
58 | CREATE SEQUENCE employees_seq;
59 | CREATE TABLE employees (
60 | id INT NOT NULL PRIMARY KEY,
61 | first_name VARCHAR( 255 ) NOT NULL ,
62 | last_name VARCHAR( 255 ) NOT NULL ,
63 | nick_name VARCHAR( 255 ) NOT NULL
64 | );
65 |
66 | CREATE SEQUENCE positions_seq;
67 | CREATE TABLE positions (
68 | id INT NOT NULL PRIMARY KEY,
69 | employee_id int NOT NULL,
70 | title VARCHAR(255) NOT NULL,
71 | active SMALLINT NOT NULL
72 | );
73 |
74 | CREATE SEQUENCE awesome_people_seq;
75 | CREATE TABLE awesome_people(
76 | id int not null primary key,
77 | author_id int,
78 | is_awesome int default 1
79 | );
80 |
81 | CREATE SEQUENCE amenities_seq;
82 | CREATE TABLE amenities(
83 | amenity_id int primary key,
84 | type varchar(40) NOT NULL
85 | );
86 |
87 | CREATE SEQUENCE property_seq;
88 | CREATE TABLE property(
89 | property_id int primary key
90 | );
91 |
92 | CREATE SEQUENCE property_amenities_seq;
93 | CREATE TABLE property_amenities(
94 | id int primary key,
95 | amenity_id int not null,
96 | property_id int not null
97 | );
98 |
99 | CREATE SEQUENCE valuestore_seq;
100 | CREATE TABLE valuestore(
101 | id int primary key,
102 | `key` varchar(20) NOT NULL DEFAULT '',
103 | `value` varchar(255) NOT NULL DEFAULT ''
104 | );
--------------------------------------------------------------------------------
/test/sql/pgsql-after-fixtures.sql:
--------------------------------------------------------------------------------
1 | SELECT setval('authors_author_id_seq', max(author_id)) FROM authors;
2 | SELECT setval('books_book_id_seq', max(book_id)) FROM books;
3 | SELECT setval('publishers_publisher_id_seq', max(publisher_id)) FROM publishers;
4 | SELECT setval('venues_id_seq', max(id)) FROM venues;
5 | SELECT setval('events_id_seq', max(id)) FROM events;
6 | SELECT setval('hosts_id_seq', max(id)) FROM hosts;
7 | SELECT setval('employees_id_seq', max(id)) FROM employees;
8 | SELECT setval('positions_id_seq', max(id)) FROM positions;
9 | SELECT setval('"rm-bldg_rm-id_seq"', max("rm-id")) FROM "rm-bldg";
10 | SELECT setval('awesome_people_id_seq', max(id)) FROM awesome_people;
11 | SELECT setval('amenities_amenity_id_seq', max(amenity_id)) FROM amenities;
12 | SELECT setval('property_property_id_seq', max(property_id)) FROM property;
13 | SELECT setval('property_amenities_id_seq', max(id)) FROM property_amenities;
14 | SELECT setval('users_id_seq', max(id)) FROM users;
15 | SELECT setval('newsletters_id_seq', max(id)) FROM newsletters;
16 | SELECT setval('user_newsletters_id_seq', max(id)) FROM user_newsletters;
17 | SELECT setval('valuestore_id_seq', max(id)) FROM valuestore;
18 |
--------------------------------------------------------------------------------
/test/sql/pgsql.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE authors(
2 | author_id SERIAL PRIMARY KEY,
3 | parent_author_id INT,
4 | publisher_id INT,
5 | name VARCHAR(25) NOT NULL DEFAULT 'default_name',
6 | updated_at timestamp,
7 | created_at timestamp,
8 | "some_Date" date,
9 | some_time time,
10 | some_text text,
11 | encrypted_password varchar(50),
12 | "mixedCaseField" varchar(50)
13 | );
14 |
15 | CREATE TABLE books(
16 | book_id SERIAL PRIMARY KEY,
17 | author_id INT,
18 | secondary_author_id INT,
19 | name VARCHAR(50),
20 | numeric_test VARCHAR(10) DEFAULT '0',
21 | special NUMERIC(10,2) DEFAULT 0.0
22 | );
23 |
24 | CREATE TABLE publishers(
25 | publisher_id SERIAL PRIMARY KEY,
26 | name VARCHAR(25) NOT NULL DEFAULT 'default_name'
27 | );
28 |
29 | CREATE TABLE venues (
30 | id SERIAL PRIMARY KEY,
31 | name varchar(50),
32 | city varchar(60),
33 | state char(2),
34 | address varchar(50),
35 | phone varchar(10) default NULL,
36 | UNIQUE(name,address)
37 | );
38 |
39 | CREATE TABLE events (
40 | id SERIAL PRIMARY KEY,
41 | venue_id int NULL,
42 | host_id int NOT NULL,
43 | title varchar(60) NOT NULL,
44 | description varchar(10),
45 | type varchar(15) default NULL
46 | );
47 |
48 | CREATE TABLE hosts(
49 | id SERIAL PRIMARY KEY,
50 | name VARCHAR(25)
51 | );
52 |
53 | CREATE TABLE employees (
54 | id SERIAL PRIMARY KEY,
55 | first_name VARCHAR(255) NOT NULL,
56 | last_name VARCHAR(255) NOT NULL,
57 | nick_name VARCHAR(255) NOT NULL
58 | );
59 |
60 | CREATE TABLE positions (
61 | id SERIAL PRIMARY KEY,
62 | employee_id int NOT NULL,
63 | title VARCHAR(255) NOT NULL,
64 | active SMALLINT NOT NULL
65 | );
66 |
67 | CREATE TABLE "rm-bldg"(
68 | "rm-id" SERIAL PRIMARY KEY,
69 | "rm-name" VARCHAR(10) NOT NULL,
70 | "space out" VARCHAR(1) NOT NULL
71 | );
72 |
73 | CREATE TABLE awesome_people(
74 | id serial primary key,
75 | author_id int,
76 | is_awesome int default 1
77 | );
78 |
79 | CREATE TABLE amenities(
80 | amenity_id serial primary key,
81 | type varchar(40) NOT NULL
82 | );
83 |
84 | CREATE TABLE property(
85 | property_id serial primary key
86 | );
87 |
88 | CREATE TABLE property_amenities(
89 | id serial primary key,
90 | amenity_id int not null,
91 | property_id int not null
92 | );
93 |
94 | CREATE TABLE users(
95 | id serial primary key
96 | );
97 |
98 | CREATE TABLE newsletters(
99 | id serial primary key
100 | );
101 |
102 | CREATE TABLE user_newsletters(
103 | id serial primary key,
104 | user_id int not null,
105 | newsletter_id int not null
106 | );
107 |
108 | CREATE TABLE valuestore (
109 | id serial primary key,
110 | key varchar(20) NOT NULL DEFAULT '',
111 | value varchar(255) NOT NULL DEFAULT ''
112 | );
113 |
114 | -- reproduces issue GH-96 for testing
115 | CREATE INDEX user_newsletters_id_and_user_id_idx ON user_newsletters USING btree(id, user_id);
116 |
--------------------------------------------------------------------------------
/test/sql/sqlite.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE authors(
2 | author_id INTEGER NOT NULL PRIMARY KEY,
3 | parent_author_id INT,
4 | publisher_id INT,
5 | name VARCHAR (25) NOT NULL DEFAULT default_name, -- don't touch those spaces
6 | updated_at datetime,
7 | created_at datetime,
8 | some_Date date,
9 | some_time time,
10 | some_text text,
11 | encrypted_password varchar(50),
12 | mixedCaseField varchar(50)
13 | );
14 |
15 | CREATE TABLE books(
16 | book_id INTEGER NOT NULL PRIMARY KEY,
17 | Author_Id INT,
18 | secondary_author_id INT,
19 | name VARCHAR(50),
20 | numeric_test VARCHAR(10) DEFAULT '0',
21 | special NUMERIC(10,2) DEFAULT 0
22 | );
23 |
24 | CREATE TABLE publishers(
25 | publisher_id INTEGER NOT NULL PRIMARY KEY,
26 | name VARCHAR (25) NOT NULL DEFAULT default_name -- don't touch those spaces
27 | );
28 |
29 | CREATE TABLE venues (
30 | Id INTEGER NOT NULL PRIMARY KEY,
31 | name varchar(50),
32 | city varchar(60),
33 | state char(2),
34 | address varchar(50),
35 | phone varchar(10) default NULL,
36 | UNIQUE(name,address)
37 | );
38 |
39 | CREATE TABLE events (
40 | id INTEGER NOT NULL PRIMARY KEY,
41 | venue_id int NULL,
42 | host_id int NOT NULL,
43 | title varchar(60) NOT NULL,
44 | description varchar(10),
45 | type varchar(15) default NULL
46 | );
47 |
48 | CREATE TABLE hosts(
49 | id INT NOT NULL PRIMARY KEY,
50 | name VARCHAR(25)
51 | );
52 |
53 | CREATE TABLE employees (
54 | id INTEGER NOT NULL PRIMARY KEY,
55 | first_name VARCHAR( 255 ) NOT NULL ,
56 | last_name VARCHAR( 255 ) NOT NULL ,
57 | nick_name VARCHAR( 255 ) NOT NULL
58 | );
59 |
60 | CREATE TABLE positions (
61 | id INTEGER NOT NULL PRIMARY KEY,
62 | employee_id int NOT NULL,
63 | title VARCHAR(255) NOT NULL,
64 | active SMALLINT NOT NULL
65 | );
66 |
67 | CREATE TABLE `rm-bldg`(
68 | `rm-id` INT NOT NULL,
69 | `rm-name` VARCHAR(10) NOT NULL,
70 | `space out` VARCHAR(1) NOT NULL
71 | );
72 |
73 | CREATE TABLE awesome_people(
74 | id integer not null primary key,
75 | author_id int,
76 | is_awesome int default 1
77 | );
78 |
79 | CREATE TABLE amenities(
80 | `amenity_id` INTEGER NOT NULL PRIMARY KEY,
81 | `type` varchar(40) DEFAULT NULL
82 | );
83 |
84 | CREATE TABLE property(
85 | `property_id` INTEGER NOT NULL PRIMARY KEY
86 | );
87 |
88 | CREATE TABLE property_amenities(
89 | `id` INTEGER NOT NULL PRIMARY KEY,
90 | `amenity_id` INT NOT NULL,
91 | `property_id` INT NOT NULL
92 | );
93 |
94 | CREATE TABLE users (
95 | id INTEGER NOT NULL PRIMARY KEY
96 | );
97 |
98 | CREATE TABLE newsletters (
99 | id INTEGER NOT NULL PRIMARY KEY
100 | );
101 |
102 | CREATE TABLE user_newsletters (
103 | id INTEGER NOT NULL PRIMARY KEY,
104 | user_id INTEGER NOT NULL,
105 | newsletter_id INTEGER NOT NULL
106 | );
107 |
108 | CREATE TABLE valuestore (
109 | `id` INTEGER NOT NULL PRIMARY KEY,
110 | `key` varchar(20) NOT NULL DEFAULT '',
111 | `value` varchar(255) NOT NULL DEFAULT ''
112 | );
113 |
--------------------------------------------------------------------------------