├── .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 | [![Build Status](https://travis-ci.org/jpfuentes2/php-activerecord.png?branch=master)](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 | * 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 | * 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 | --------------------------------------------------------------------------------