diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..412e047bd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,125 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + +# A newer push to the same branch or pull request cancels the run it supersedes. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + phpunit: + name: PHP ${{ matrix.php }} / ${{ matrix.mariadb && format('MariaDB {0}', matrix.mariadb) || format('MySQL {0}', matrix.mysql) }} / PostgreSQL ${{ matrix.pgsql }} / default ${{ matrix.adapter || 'mysql' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + # One baseline (PHP 8.4, MySQL 8.4, PostgreSQL 18); every other job + # changes exactly one axis so a failure points at a single variable. + # The model-level tests run against the default adapter (PHPAR_ADAPTER), + # MySQL unless a row says otherwise; the adapter tests always cover all + # three. SQLite is bundled with each PHP build and is exercised in every job. + matrix: + include: + - { php: '8.4', mysql: '8.4', pgsql: '18' } + # MySQL axis + - { php: '8.4', mysql: '5.7', pgsql: '18' } + - { php: '8.4', mysql: '8.0', pgsql: '18' } + # MariaDB, through the same adapter: 10.11 (Debian 12, Ubuntu 24.04, + # RHEL 9/10), 11.8 (Debian 13) and the current 12.3 LTS. + - { php: '8.4', mariadb: '10.11', pgsql: '18' } + - { php: '8.4', mariadb: '11.8', pgsql: '18' } + - { php: '8.4', mariadb: '12.3', pgsql: '18' } + # PostgreSQL axis, with PostgreSQL as the default adapter + - { php: '8.4', mysql: '8.4', pgsql: '14', adapter: pgsql } + - { php: '8.4', mysql: '8.4', pgsql: '16', adapter: pgsql } + - { php: '8.4', mysql: '8.4', pgsql: '18', adapter: pgsql } + # PHP axis + - { php: '8.2', mysql: '8.4', pgsql: '18' } + - { php: '8.3', mysql: '8.4', pgsql: '18' } + - { php: '8.5', mysql: '8.4', pgsql: '18' } + + # Data directories live on tmpfs: the suite commits tens of thousands of + # tiny transactions and each one fsyncs, which is slow on the runner's + # network-backed disk. The databases are throwaway, so nothing is lost. + services: + mysql: + # The MariaDB image accepts the MYSQL_* variables and speaks the same protocol. + image: ${{ matrix.mariadb && format('mariadb:{0}', matrix.mariadb) || format('mysql:{0}', matrix.mysql) }} + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + ports: + - 3306:3306 + options: >- + --tmpfs /var/lib/mysql:rw + --health-cmd "${{ matrix.mariadb && 'healthcheck.sh --connect --innodb_initialized' || 'mysqladmin ping -h 127.0.0.1 -uroot -proot' }}" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + + postgres: + image: postgres:${{ matrix.pgsql }} + env: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test + # Keep the data directory on the tmpfs above. The image default is a + # declared volume (/var/lib/postgresql/data up to 17, a versioned + # subdirectory from 18), and 18 refuses a mount at the old path. + PGDATA: /var/lib/postgresql/pgdata + ports: + - 5432:5432 + options: >- + --tmpfs /var/lib/postgresql:rw + --health-cmd "pg_isready -h 127.0.0.1 -U test" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + + memcached: + image: memcached:1.6-alpine + ports: + - 11211:11211 + + steps: + - uses: actions/checkout@v7 + + - name: Set up PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: pdo_mysql, pdo_pgsql, pdo_sqlite, memcached + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Run tests + run: vendor/bin/phpunit + env: + PHPAR_ADAPTER: ${{ matrix.adapter || 'mysql' }} + PHPAR_MYSQL: mysql://test:test@127.0.0.1/test + PHPAR_PGSQL: pgsql://test:test@127.0.0.1/test + + phpcs: + name: Coding standard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Run phpcs + run: vendor/bin/phpcs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ab112fc18..000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -install: composer install --prefer-source --dev - -services: - - memcache - -env: PHPAR_MYSQL=mysql://root@127.0.0.1/phpar_test PHPAR_PGSQL=pgsql://postgres@127.0.0.1/phpar_test - -language: php -php: - - 5.3 - - 5.4 - - 5.5 - - 7.0 - - 7.1 - - nightly - -matrix: - include: - - php: hhvm - dist: trusty - allow_failures: - - php: hhvm - - php: nightly - fast_finish: true - -before_script: - - | - if [[ "${TRAVIS_PHP_VERSION:0:1}" == "7" ]]; then - curl -L https://github.com/websupport-sk/pecl-memcache/archive/NON_BLOCKING_IO_php7.tar.gz | tar xz; - (cd pecl-memcache-NON_BLOCKING_IO_php7 && phpize && ./configure && make && make install); - fi - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo 'extension = "memcache.so"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi - - - mysql -e 'CREATE DATABASE phpar_test;' - - psql -c 'CREATE DATABASE phpar_test;' -U postgres - -script: ./vendor/bin/phpunit diff --git a/CHANGELOG b/CHANGELOG index ced62ee43..fc2e2af3c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,6 @@ Version 1.0 - June 27, 2010 Versio 1.0 RC1 - May 7, 2010 -- support for Oracle - support for PostgreSQL - added delegators - added setters diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d1cb1478..08ea75e3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,17 +27,41 @@ Or run a single test file by specifying its path: vendor/bin/phpunit test/InflectorTest.php ``` -#### Skipped Tests #### +#### Databases #### -You might notice that some tests are marked as skipped. To obtain more information about skipped -tests, pass the `--verbose` flag to PHPUnit: +Most tests need a MySQL server, and the PostgreSQL and memcached tests are skipped when their server +or PHP extension is missing. To see why a test was skipped: ```sh -vendor/bin/phpunit --verbose +vendor/bin/phpunit --display-skipped ``` -Some common steps for fixing skipped tests are to: +The suite connects to `mysql://test:test@127.0.0.1/test` and `pgsql://test:test@127.0.0.1/test` by +default, and to memcached on `localhost:11211`. Override the database locations with the `PHPAR_MYSQL`, +`PHPAR_PGSQL` and `PHPAR_SQLITE` environment variables. The model-level tests run against MySQL unless +`PHPAR_ADAPTER` names another connection (`pgsql` or `sqlite`); the adapter tests always cover all three. -* Install `memcached` and the PHP memcached extension (e.g., `brew install php56-memcache memcached` on macOS) -* Install the PDO drivers for PostgreSQL (e.g., `brew install php56-pdo-pgsql` on macOS) -* Create a MySQL database and a PostgreSQL database. You can either create these such that they are available at the default locations of `mysql://test:test@127.0.0.1/test` and `pgsql://test:test@127.0.0.1/test` respectively. Alternatively, you can set the `PHPAR_MYSQL` and `PHPAR_PGSQL` environment variables to specify a different location for the MySQL and PostgreSQL databases. +The easiest way to get the servers is Docker. `compose.yaml` defines the same versions that CI tests, +each on its own port, so you can run several side by side: + +```sh +docker compose up -d mysql84 pgsql18 memcached +PHPAR_MYSQL=mysql://test:test@127.0.0.1:33084/test \ +PHPAR_PGSQL=pgsql://test:test@127.0.0.1:54318/test \ +vendor/bin/phpunit +``` + +Ports follow the version: `mysql57` is 33057, `mysql80` is 33080, `mysql84` is 33084, `mariadb1011` is +33111, `mariadb118` is 33118, `mariadb123` is 33123, `pgsql14` is 54314, `pgsql16` is 54316 and `pgsql18` +is 54318. MariaDB uses the MySQL adapter, so point `PHPAR_MYSQL` at it. The containers keep their data on tmpfs and skip fsyncs, so +they start empty every time and run fast even when Docker lives inside a VM. + +You also need the PHP extensions `pdo_mysql`, `pdo_pgsql`, `pdo_sqlite` and `memcached`. + +#### Continuous integration #### + +`.github/workflows/tests.yml` runs the suite on GitHub Actions against a small matrix: a baseline of +PHP 8.4, MySQL 8.4 and PostgreSQL 18, plus one job per other supported version of each, changing a +single axis at a time, and three MariaDB versions through the MySQL adapter. The PostgreSQL rows run +the model-level tests with PostgreSQL as the default adapter. A separate job runs `vendor/bin/phpcs`, so a pull request that breaks the coding standard fails +CI as well. Update the matrix when a PHP or database version reaches end of life. diff --git a/README.md b/README.md index 9f7fe5a4a..05f0cf99c 100755 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ Of course, there are some differences which will be obvious to the user if they - MySQL - SQLite - PostgreSQL -- Oracle ## Features ## @@ -65,11 +64,11 @@ ActiveRecord\Config::initialize(function($cfg) { $cfg->set_model_directory('/path/to/your/model_directory'); $cfg->set_connections( - array( + [ 'development' => 'mysql://username:password@localhost/development_database_name', 'test' => 'mysql://username:password@localhost/test_database_name', 'production' => 'mysql://username:password@localhost/production_database_name' - ) + ] ); }); ``` @@ -80,11 +79,11 @@ Alternatively (w/o the 5.3 closure): $cfg = ActiveRecord\Config::instance(); $cfg->set_model_directory('/path/to/your/model_directory'); $cfg->set_connections( - array( + [ 'development' => 'mysql://username:password@localhost/development_database_name', 'test' => 'mysql://username:password@localhost/test_database_name', 'production' => 'mysql://username:password@localhost/production_database_name' - ) + ] ); ``` @@ -122,7 +121,7 @@ $post = Post::find_by_name_and_id('The Bridge Builder',100); $post = Post::find_by_name_or_id('The Bridge Builder',100); # finding using a conditions array -$posts = Post::find('all',array('conditions' => array('name=? or id > ?','The Bridge Builder',100))); +$posts = Post::find('all',['conditions' => ['name=? or id > ?','The Bridge Builder',100]]); ``` ### Create ### diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..d86d0386f --- /dev/null +++ b/compose.yaml @@ -0,0 +1,75 @@ +# Database servers for running the test suite locally, mirroring the versions +# tested in .github/workflows/tests.yml. Start only what you need, e.g.: +# +# docker compose up -d mysql84 pgsql18 memcached +# PHPAR_MYSQL=mysql://test:test@127.0.0.1:33084/test \ +# PHPAR_PGSQL=pgsql://test:test@127.0.0.1:54318/test \ +# vendor/bin/phpunit +# +# The databases are throwaway, so they keep their data on tmpfs and skip +# per-commit fsyncs. That is the difference between a one-minute and a +# five-second run when Docker runs inside a VM (macOS, Windows). + +x-mysql: &mysql + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + tmpfs: + - /var/lib/mysql + command: --innodb-flush-log-at-trx-commit=0 --innodb-doublewrite=0 --skip-log-bin + +x-pgsql: &pgsql + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test + PGDATA: /var/lib/postgresql/pgdata # see the note in the workflow file + tmpfs: + - /var/lib/postgresql + command: -c fsync=off -c synchronous_commit=off -c full_page_writes=off + +services: + mysql57: + <<: *mysql + image: mysql:5.7 + platform: linux/amd64 # no arm64 image exists; runs emulated on Apple silicon + ports: ["127.0.0.1:33057:3306"] + mysql80: + <<: *mysql + image: mysql:8.0 + ports: ["127.0.0.1:33080:3306"] + mysql84: + <<: *mysql + image: mysql:8.4 + ports: ["127.0.0.1:33084:3306"] + mariadb1011: + <<: *mysql + image: mariadb:10.11 + ports: ["127.0.0.1:33111:3306"] + mariadb118: + <<: *mysql + image: mariadb:11.8 + ports: ["127.0.0.1:33118:3306"] + mariadb123: + <<: *mysql + image: mariadb:12.3 + ports: ["127.0.0.1:33123:3306"] + + pgsql14: + <<: *pgsql + image: postgres:14 + ports: ["127.0.0.1:54314:5432"] + pgsql16: + <<: *pgsql + image: postgres:16 + ports: ["127.0.0.1:54316:5432"] + pgsql18: + <<: *pgsql + image: postgres:18 + ports: ["127.0.0.1:54318:5432"] + + memcached: + image: memcached:1.6-alpine + ports: ["127.0.0.1:11211:11211"] diff --git a/examples/orders/models/Order.php b/examples/orders/models/Order.php index 642943c4b..7d53d88a5 100644 --- a/examples/orders/models/Order.php +++ b/examples/orders/models/Order.php @@ -1,37 +1,38 @@ 'payments', - 'select' => 'people.*, payments.amount', - 'conditions' => 'payments.amount < 200')); + // order can have many payments by many people + // the conditions is just there as an example as it makes no logical sense + public static $has_many = [ + ['payments'], + ['people', + 'through' => 'payments', + 'select' => 'people.*, payments.amount', + 'conditions' => 'payments.amount < 200']]; - // order must have a price and tax > 0 - static $validates_numericality_of = array( - array('price', 'greater_than' => 0), - array('tax', 'greater_than' => 0)); + // order must have a price and tax > 0 + public static $validates_numericality_of = [ + ['price', 'greater_than' => 0], + ['tax', 'greater_than' => 0]]; - // setup a callback to automatically apply a tax - static $before_validation_on_create = array('apply_tax'); + // setup a callback to automatically apply a tax + public static $before_validation_on_create = ['apply_tax']; - public function apply_tax() - { - if ($this->person->state == 'VA') - $tax = 0.045; - elseif ($this->person->state == 'CA') - $tax = 0.10; - else - $tax = 0.02; + public function apply_tax() + { + if ($this->person->state == 'VA') { + $tax = 0.045; + } elseif ($this->person->state == 'CA') { + $tax = 0.10; + } else { + $tax = 0.02; + } - $this->tax = $this->price * $tax; - } + $this->tax = $this->price * $tax; + } } -?> diff --git a/examples/orders/models/Payment.php b/examples/orders/models/Payment.php index faef5ef91..ab26f7a22 100644 --- a/examples/orders/models/Payment.php +++ b/examples/orders/models/Payment.php @@ -1,9 +1,9 @@ diff --git a/examples/orders/models/Person.php b/examples/orders/models/Person.php index b2e586064..d2acdc788 100644 --- a/examples/orders/models/Person.php +++ b/examples/orders/models/Person.php @@ -1,13 +1,13 @@ diff --git a/examples/orders/orders.php b/examples/orders/orders.php index 8222ad448..1f92e99ee 100644 --- a/examples/orders/orders.php +++ b/examples/orders/orders.php @@ -1,79 +1,78 @@ set_model_directory(__DIR__ . '/models'); - $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/orders_test')); + $cfg->set_connections(['development' => 'mysql://test:test@127.0.0.1/orders_test']); - // you can change the default connection with the below + // you can change the default connection with the below //$cfg->set_default_connection('production'); }); // create some people -$jax = new Person(array('name' => 'Jax', 'state' => 'CA')); +$jax = new Person(['name' => 'Jax', 'state' => 'CA']); $jax->save(); // compact way to create and save a model -$tito = Person::create(array('name' => 'Tito', 'state' => 'VA')); +$tito = Person::create(['name' => 'Tito', 'state' => 'VA']); // place orders. tax is automatically applied in a callback // create_orders will automatically place the created model into $tito->orders // even if it failed validation -$pokemon = $tito->create_orders(array('item_name' => 'Live Pokemon', 'price' => 6999.99)); -$coal = $tito->create_orders(array('item_name' => 'Lump of Coal', 'price' => 100.00)); -$freebie = $tito->create_orders(array('item_name' => 'Freebie', 'price' => -100.99)); +$pokemon = $tito->create_orders(['item_name' => 'Live Pokemon', 'price' => 6999.99]); +$coal = $tito->create_orders(['item_name' => 'Lump of Coal', 'price' => 100.00]); +$freebie = $tito->create_orders(['item_name' => 'Freebie', 'price' => -100.99]); -if (count($freebie->errors) > 0) - echo "[FAILED] saving order $freebie->item_name: " . join(', ',$freebie->errors->full_messages()) . "\n\n"; +if (count($freebie->errors) > 0) { + echo "[FAILED] saving order $freebie->item_name: " . implode(', ', $freebie->errors->full_messages()) . "\n\n"; +} // payments -$pokemon->create_payments(array('amount' => 1.99, 'person_id' => $tito->id)); -$pokemon->create_payments(array('amount' => 4999.50, 'person_id' => $tito->id)); -$pokemon->create_payments(array('amount' => 2.50, 'person_id' => $jax->id)); +$pokemon->create_payments(['amount' => 1.99, 'person_id' => $tito->id]); +$pokemon->create_payments(['amount' => 4999.50, 'person_id' => $tito->id]); +$pokemon->create_payments(['amount' => 2.50, 'person_id' => $jax->id]); // reload since we don't want the freebie to show up (because it failed validation) $tito->reload(); -echo "$tito->name has " . count($tito->orders) . " orders for: " . join(', ',ActiveRecord\collect($tito->orders,'item_name')) . "\n\n"; +echo "$tito->name has " . count($tito->orders) . " orders for: " . implode(', ', ActiveRecord\collect($tito->orders, 'item_name')) . "\n\n"; // get all orders placed by Tito -foreach (Order::find_all_by_person_id($tito->id) as $order) -{ - echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax) ordered by " . $order->person->name . "\n"; - - if (count($order->payments) > 0) - { - // display each payment for this order - foreach ($order->payments as $payment) - echo " payment #$payment->id of $$payment->amount by " . $payment->person->name . "\n"; - } - else - echo " no payments\n"; - - echo "\n"; +foreach (Order::find_all_by_person_id($tito->id) as $order) { + echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax) ordered by " . $order->person->name . "\n"; + + if (count($order->payments) > 0) { + // display each payment for this order + foreach ($order->payments as $payment) { + echo " payment #$payment->id of $$payment->amount by " . $payment->person->name . "\n"; + } + } else { + echo " no payments\n"; + } + + echo "\n"; } // display summary of all payments made by Tito and Jax -$conditions = array( - 'conditions' => array('id IN(?)',array($tito->id,$jax->id)), - 'order' => 'name desc'); - -foreach (Person::all($conditions) as $person) -{ - $n = count($person->payments); - $total = array_sum(ActiveRecord\collect($person->payments,'amount')); - echo "$person->name made $n payments for a total of $$total\n\n"; +$conditions = [ + 'conditions' => ['id IN(?)',[$tito->id,$jax->id]], + 'order' => 'name desc']; + +foreach (Person::all($conditions) as $person) { + $n = count($person->payments); + $total = array_sum(ActiveRecord\collect($person->payments, 'amount')); + echo "$person->name made $n payments for a total of $$total\n\n"; } // using order has_many people through payments with options -// array('people', 'through' => 'payments', 'select' => 'people.*, payments.amount', 'conditions' => 'payments.amount < 200')); +// ['people', 'through' => 'payments', 'select' => 'people.*, payments.amount', 'conditions' => 'payments.amount < 200']); // this means our people in the loop below also has the payment information since it is part of an inner join // we will only see 2 of the people instead of 3 because 1 of the payments is greater than 200 $order = Order::find($pokemon->id); echo "Order #$order->id for $order->item_name ($$order->price + $$order->tax tax)\n"; -foreach ($order->people as $person) - echo " payment of $$person->amount by " . $person->name . "\n"; -?> +foreach ($order->people as $person) { + echo " payment of $$person->amount by " . $person->name . "\n"; +} diff --git a/examples/simple/simple.php b/examples/simple/simple.php index 0acff65d2..025f74936 100644 --- a/examples/simple/simple.php +++ b/examples/simple/simple.php @@ -1,17 +1,18 @@ set_model_directory('.'); - $cfg->set_connections(array('development' => 'mysql://test:test@127.0.0.1/test')); + $cfg->set_connections(['development' => 'mysql://test:test@127.0.0.1/test']); }); print_r(Book::first()->attributes()); -?> diff --git a/examples/simple/simple_with_options.php b/examples/simple/simple_with_options.php index 1056435f7..eda6e78e2 100644 --- a/examples/simple/simple_with_options.php +++ b/examples/simple/simple_with_options.php @@ -1,32 +1,31 @@ db.table_name - static $db = 'test'; + // explicit database name will generate sql like so => db.table_name + public static $db = 'test'; } -$connections = array( - 'development' => 'mysql://invalid', - 'production' => 'mysql://test:test@127.0.0.1/test' -); +$connections = [ + 'development' => 'mysql://invalid', + 'production' => 'mysql://test:test@127.0.0.1/test' +]; // initialize ActiveRecord -ActiveRecord\Config::initialize(function($cfg) use ($connections) -{ +ActiveRecord\Config::initialize(function ($cfg) use ($connections) { $cfg->set_model_directory('.'); $cfg->set_connections($connections); }); print_r(Book::first()->attributes()); -?> diff --git a/lib/AbstractRelationship.php b/lib/AbstractRelationship.php index 99a0f15f2..40153b4ee 100644 --- a/lib/AbstractRelationship.php +++ b/lib/AbstractRelationship.php @@ -38,14 +38,14 @@ abstract class AbstractRelationship implements RelationshipInterface * * @var string */ - public $foreign_key = array(); + public $foreign_key = []; /** * Options of the relationship. * * @var array */ - protected $options = array(); + protected $options = []; /** * Is the relationship single or multi. @@ -59,7 +59,7 @@ abstract class AbstractRelationship implements RelationshipInterface * * @var array */ - protected static $valid_association_options = array('class_name', 'class', 'foreign_key', 'conditions', 'select', 'readonly', 'namespace'); + protected static $valid_association_options = ['class_name', 'class', 'foreign_key', 'conditions', 'select', 'readonly', 'namespace']; /** * Constructs a relationship. @@ -67,7 +67,7 @@ abstract class AbstractRelationship implements RelationshipInterface * @param array $options Options for the relationship (see {@link valid_association_options}) * @return mixed */ - public function __construct($options = array()) + public function __construct($options = []) { $this->attribute_name = $options[0]; $this->options = $this->merge_association_options($options); @@ -79,7 +79,7 @@ public function __construct($options = array()) } if (isset($this->options['conditions']) && !is_array($this->options['conditions'])) { - $this->options['conditions'] = array($this->options['conditions']); + $this->options['conditions'] = [$this->options['conditions']]; } if (isset($this->options['class'])) { @@ -91,7 +91,7 @@ public function __construct($options = array()) $this->attribute_name = strtolower(Inflector::instance()->variablize($this->attribute_name)); if (!$this->foreign_key && isset($this->options['foreign_key'])) { - $this->foreign_key = is_array($this->options['foreign_key']) ? $this->options['foreign_key'] : array($this->options['foreign_key']); + $this->foreign_key = is_array($this->options['foreign_key']) ? $this->options['foreign_key'] : [$this->options['foreign_key']]; } } @@ -125,9 +125,12 @@ public function is_poly() * @param $model_values_keys -> key(s)/value(s) to be used in query from model which is including * @return void */ - protected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes = array(), $query_keys = array(), $model_values_keys = array()) + protected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes = [], $query_keys = [], $model_values_keys = []) { - $values = array(); + // Query the related table by its real column names; the relationship keys are inflected. + $query_keys = array_map([$this->get_table(), 'column_name_for'], (array)$query_keys); + + $values = []; $options = $this->options; $inflector = Inflector::instance(); $query_key = $query_keys[0]; @@ -137,7 +140,7 @@ protected function query_and_attach_related_models_eagerly(Table $table, $models $values[] = $value[$inflector->variablize($model_values_key)]; } - $values = array($values); + $values = [$values]; $conditions = SQLBuilder::create_conditions_from_underscored_string($table->conn, $query_key, $values); if (isset($options['conditions']) && strlen($options['conditions'][0]) > 1) { @@ -183,8 +186,8 @@ protected function query_and_attach_related_models_eagerly(Table $table, $models $class = $this->class_name; $related_models = $class::find('all', $options); - $used_models_map = array(); - $related_models_map = array(); + $used_models_map = []; + $related_models_map = []; $model_values_key = $inflector->variablize($model_values_key); $query_key = $inflector->variablize($query_key); @@ -220,7 +223,7 @@ protected function query_and_attach_related_models_eagerly(Table $table, $models * @param array $attributes Hash containing attributes to initialize the model with * @return Model */ - public function build_association(Model $model, $attributes = array(), $guard_attributes = true) + public function build_association(Model $model, $attributes = [], $guard_attributes = true) { $class_name = $this->class_name; return new $class_name($attributes, $guard_attributes); @@ -233,7 +236,7 @@ public function build_association(Model $model, $attributes = array(), $guard_at * @param array $attributes Hash containing attributes to initialize the model with * @return Model */ - public function create_association(Model $model, $attributes = array(), $guard_attributes = true) + public function create_association(Model $model, $attributes = [], $guard_attributes = true) { $class_name = $this->class_name; $new_record = $class_name::create($attributes, true, $guard_attributes); @@ -304,7 +307,7 @@ protected function set_class_name($class_name) $this->class_name = $class_name; } - protected function create_conditions_from_keys(Model $model, $condition_keys = array(), $value_keys = array()) + protected function create_conditions_from_keys(Model $model, $condition_keys = [], $value_keys = []) { $condition_string = implode('_and_', $condition_keys); $condition_values = array_values($model->get_values_for($value_keys)); @@ -320,7 +323,7 @@ protected function create_conditions_from_keys(Model $model, $condition_keys = a if (isset($this->options['conditions'])) { $options_conditions = $this->options['conditions']; } else { - $options_conditions = array(); + $options_conditions = []; } return Utils::add_condition($options_conditions, $conditions); @@ -379,5 +382,3 @@ public function construct_inner_join_sql(Table $from_table, $using_through = fal */ abstract public function load(Model $model); } - - diff --git a/lib/Adapters/MysqlAdapter.php b/lib/Adapters/MysqlAdapter.php index d83a07b2f..39e2c2144 100644 --- a/lib/Adapters/MysqlAdapter.php +++ b/lib/Adapters/MysqlAdapter.php @@ -17,7 +17,7 @@ */ class MysqlAdapter extends Connection { - static $DEFAULT_PORT = 3306; + public static $DEFAULT_PORT = 3306; public function limit($sql, $offset, $limit) { @@ -72,7 +72,7 @@ public function create_column(&$column) public function set_encoding($charset) { - $params = array($charset); + $params = [$charset]; $this->query('SET NAMES ?', $params); } diff --git a/lib/Adapters/OciAdapter.php b/lib/Adapters/OciAdapter.php deleted file mode 100644 index 330b9523f..000000000 --- a/lib/Adapters/OciAdapter.php +++ /dev/null @@ -1,157 +0,0 @@ -dsn_params = isset($info->charset) ? ";charset=$info->charset" : ""; - $this->connection = new PDO("oci:dbname=//$info->host/$info->db$this->dsn_params", $info->user, $info->pass, static::$PDO_OPTIONS); - } catch (PDOException $e) { - throw new DatabaseException($e); - } - } - - public function supports_sequences() - { - return true; - } - - public function get_next_sequence_value($sequence_name) - { - return $this->query_and_fetch_one('SELECT ' . $this->next_sequence_value($sequence_name) . ' FROM dual'); - } - - public function next_sequence_value($sequence_name) - { - return "$sequence_name.nextval"; - } - - public function date_to_string($datetime) - { - return $datetime->format('d-M-Y'); - } - - public function datetime_to_string($datetime) - { - return $datetime->format('d-M-Y h:i:s A'); - } - - // $string = DD-MON-YYYY HH12:MI:SS(\.[0-9]+) AM - public function string_to_datetime($string) - { - return parent::string_to_datetime(str_replace('.000000', '', $string)); - } - - public function limit($sql, $offset, $limit) - { - $offset = intval($offset); - $stop = $offset + intval($limit); - return - "SELECT * FROM (SELECT a.*, rownum ar_rnum__ FROM ($sql) a " . - "WHERE rownum <= $stop) WHERE ar_rnum__ > $offset"; - } - - public function query_column_info($table) - { - $sql = - "SELECT c.column_name, c.data_type, c.data_length, c.data_scale, c.data_default, c.nullable, " . - "(SELECT a.constraint_type " . - "FROM all_constraints a, all_cons_columns b " . - "WHERE a.constraint_type='P' " . - "AND a.constraint_name=b.constraint_name " . - "AND a.table_name = t.table_name AND b.column_name=c.column_name) AS pk " . - "FROM user_tables t " . - "INNER JOIN user_tab_columns c on(t.table_name=c.table_name) " . - "WHERE t.table_name=?"; - - $values = array(strtoupper($table)); - return $this->query($sql, $values); - } - - public function query_for_tables() - { - return $this->query("SELECT table_name FROM user_tables"); - } - - public function create_column(&$column) - { - $column['column_name'] = strtolower($column['column_name']); - $column['data_type'] = strtolower(preg_replace('/\(.*?\)/', '', $column['data_type'])); - - if ($column['data_default'] !== null) { - $column['data_default'] = trim($column['data_default'], "' "); - } - - if ($column['data_type'] == 'number') { - if ($column['data_scale'] > 0) { - $column['data_type'] = 'decimal'; - } elseif ($column['data_scale'] == 0) { - $column['data_type'] = 'int'; - } - } - - $c = new Column(); - $c->inflected_name = Inflector::instance()->variablize($column['column_name']); - $c->name = $column['column_name']; - $c->nullable = $column['nullable'] == 'Y' ? true : false; - $c->pk = $column['pk'] == 'P' ? true : false; - $c->length = $column['data_length']; - - if ($column['data_type'] == 'timestamp') { - $c->raw_type = 'datetime'; - } else { - $c->raw_type = $column['data_type']; - } - - $c->map_raw_type(); - $c->default = $c->cast($column['data_default'], $this); - - return $c; - } - - public function set_encoding($charset) - { - // is handled in the constructor - } - - public function native_database_types() - { - return [ - 'primary_key' => "NUMBER(38) NOT NULL PRIMARY KEY", - 'string' => ['name' => 'VARCHAR2', 'length' => 255], - 'text' => ['name' => 'CLOB'], - 'integer' => ['name' => 'NUMBER', 'length' => 38], - 'float' => ['name' => 'NUMBER'], - 'datetime' => ['name' => 'DATE'], - 'timestamp' => ['name' => 'DATE'], - 'time' => ['name' => 'DATE'], - 'date' => ['name' => 'DATE'], - 'binary' => ['name' => 'BLOB'], - 'boolean' => ['name' => 'NUMBER', 'length' => 1] - ]; - } -} diff --git a/lib/Adapters/PgsqlAdapter.php b/lib/Adapters/PgsqlAdapter.php index 9a02cdf02..d92642a8e 100644 --- a/lib/Adapters/PgsqlAdapter.php +++ b/lib/Adapters/PgsqlAdapter.php @@ -17,8 +17,8 @@ */ class PgsqlAdapter extends Connection { - static $QUOTE_CHARACTER = '"'; - static $DEFAULT_PORT = 5432; + public static $QUOTE_CHARACTER = '"'; + public static $DEFAULT_PORT = 5432; public function supports_sequences() { @@ -54,7 +54,7 @@ public function query_column_info($table) AND a.attnum = ANY (pg_index.indkey) AND pg_index.indisprimary = 't' ) IS NOT NULL AS pk, - REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE((SELECT pg_attrdef.adsrc + REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_REPLACE((SELECT pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid) FROM pg_attrdef WHERE c.oid = pg_attrdef.adrelid AND pg_attrdef.adnum=a.attnum @@ -66,7 +66,7 @@ public function query_column_info($table) AND a.atttypid = t.oid ORDER BY a.attnum SQL; - $values = array($table); + $values = [$table]; return $this->query($sql, $values); } diff --git a/lib/Adapters/SqliteAdapter.php b/lib/Adapters/SqliteAdapter.php index f4340c10c..7196ea95f 100644 --- a/lib/Adapters/SqliteAdapter.php +++ b/lib/Adapters/SqliteAdapter.php @@ -21,8 +21,6 @@ */ class SqliteAdapter extends Connection { - static $datetime_format = 'Y-m-d H:i:s'; - protected function __construct($info) { if (!file_exists($info->host)) { @@ -58,11 +56,11 @@ public function create_column($column) $c->pk = $column['pk'] ? true : false; $c->auto_increment = in_array( strtoupper($column['type']), - array('INT', 'INTEGER') + ['INT', 'INTEGER'] ) && $c->pk; $column['type'] = preg_replace('/ +/', ' ', $column['type']); - $column['type'] = str_replace(array('(',')'), ' ', $column['type']); + $column['type'] = str_replace(['(',')'], ' ', $column['type']); $column['type'] = Utils::squeeze(' ', $column['type']); $matches = explode(' ', $column['type']); diff --git a/lib/Cache.php b/lib/Cache.php index 85b3742d1..8f639135a 100644 --- a/lib/Cache.php +++ b/lib/Cache.php @@ -3,6 +3,7 @@ namespace ActiveRecord; use Closure; +use ActiveRecord\Exceptions\CacheException; /** * Cache::get('the-cache-key', function() { @@ -12,8 +13,8 @@ */ class Cache { - static $adapter = null; - static $options = array(); + public static $adapter = null; + public static $options = []; /** * Initializes the cache. @@ -27,10 +28,10 @@ class Cache * * Ex: * $cfg_ar = ActiveRecord\Config::instance(); - * $cfg_ar->set_cache('memcache://localhost:11211',array( + * $cfg_ar->set_cache('memcached://localhost:11211',[ * 'namespace' => 'my_cool_app', * 'expire' => 120 - * )); + * ]); * * In the example above all the keys expire after 120 seconds, and the * all get a postfix 'my_cool_app'. @@ -40,19 +41,22 @@ class Cache * @param string $url URL to your cache server * @param array $options Specify additional options */ - public static function initialize($url, $options = array()) + public static function initialize($url, $options = []) { if ($url) { $url = parse_url($url); - $file = ucwords(Inflector::instance()->camelize($url['scheme'])); - $class = "ActiveRecord\\$file"; - require_once __DIR__ . "/cache/$file.php"; + $class = __NAMESPACE__ . '\\Cache\\' . ucwords(Inflector::instance()->camelize($url['scheme'])); + + if (!class_exists($class)) { + throw new CacheException("Unsupported cache adapter: {$url['scheme']}"); + } + static::$adapter = new $class($url); } else { static::$adapter = null; } - static::$options = array_merge(array('expire' => 30, 'namespace' => ''), $options); + static::$options = array_merge(['expire' => 30, 'namespace' => ''], $options); } public static function flush() diff --git a/lib/cache/Memcached.php b/lib/Cache/Memcached.php similarity index 94% rename from lib/cache/Memcached.php rename to lib/Cache/Memcached.php index 0629fabc8..04600689b 100644 --- a/lib/cache/Memcached.php +++ b/lib/Cache/Memcached.php @@ -1,6 +1,8 @@ * class Person extends ActiveRecord\Model { - * static $before_save = array('make_name_uppercase'); - * static $after_save = array('do_happy_dance'); + * static $before_save = ['make_name_uppercase']; + * static $after_save = ['do_happy_dance']; * * public function make_name_uppercase() { * $this->name = strtoupper($this->name); @@ -67,7 +67,7 @@ class CallBack * * @var array */ - protected static $VALID_CALLBACKS = array( + protected static $VALID_CALLBACKS = [ 'after_construct', 'before_save', 'after_save', @@ -83,7 +83,7 @@ class CallBack 'after_validation_on_update', 'before_destroy', 'after_destroy' - ); + ]; /** * Container for reflection class of given model @@ -103,7 +103,7 @@ class CallBack * * @var array */ - private $registry = array(); + private $registry = []; /** * Creates a CallBack. @@ -119,7 +119,7 @@ public function __construct($model_class_name) // look for explicitly defined static callback if (($definition = $this->klass->getStaticPropertyValue($name, null))) { if (!is_array($definition)) { - $definition = array($definition); + $definition = [$definition]; } foreach ($definition as $method_name) { @@ -168,7 +168,7 @@ public function invoke($model, $name, $must_exist = true) // if it doesn't exist it might be a /(after|before)_(create|update)/ so we still need to run the save // callback if (!array_key_exists($name, $this->registry)) { - $registry = array(); + $registry = []; } else { $registry = $this->registry[$name]; } @@ -177,13 +177,13 @@ public function invoke($model, $name, $must_exist = true) // starts with /(after|before)_(create|update)/ if (($first == 'after_' || $first == 'before') && (($second = substr($name, 7, 5)) == 'creat' || $second == 'updat' || $second == 'reate' || $second == 'pdate')) { - $temporal_save = str_replace(array('create', 'update'), 'save', $name); + $temporal_save = str_replace(['create', 'update'], 'save', $name); if (!isset($this->registry[$temporal_save])) { - $this->registry[$temporal_save] = array(); + $this->registry[$temporal_save] = []; } - $registry = array_merge($this->registry[$temporal_save], $registry ? $registry : array()); + $registry = array_merge($this->registry[$temporal_save], $registry ? $registry : []); } if ($registry) { @@ -212,9 +212,9 @@ public function invoke($model, $name, $must_exist = true) * @return void * @throws ActiveRecordException if invalid callback type or callback method was not found */ - public function register($name, $closure_or_method_name = null, $options = array()) + public function register($name, $closure_or_method_name = null, $options = []) { - $options = array_merge(array('prepend' => false), $options); + $options = array_merge(['prepend' => false], $options); if (!$closure_or_method_name) { $closure_or_method_name = $name; @@ -243,7 +243,7 @@ public function register($name, $closure_or_method_name = null, $options = array } if (!isset($this->registry[$name])) { - $this->registry[$name] = array(); + $this->registry[$name] = []; } if ($options['prepend']) { diff --git a/lib/Column.php b/lib/Column.php index 330fa598f..32a1d370c 100644 --- a/lib/Column.php +++ b/lib/Column.php @@ -26,7 +26,7 @@ class Column * @static * @var array */ - static $TYPE_MAPPING = [ + public static $TYPE_MAPPING = [ 'datetime' => self::DATETIME, 'timestamp' => self::DATETIME, 'date' => self::DATE, diff --git a/lib/Config.php b/lib/Config.php index 49fea7670..4e7bf7526 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -15,9 +15,9 @@ * * ActiveRecord::initialize(function($cfg) { * $cfg->set_model_home('models'); - * $cfg->set_connections(array( + * $cfg->set_connections([ * 'development' => 'mysql://user:pass@development.com/awesome_development', - * 'production' => 'mysql://user:pass@production.com/awesome_production')); + * 'production' => 'mysql://user:pass@production.com/awesome_production']); * }); * * @@ -31,9 +31,9 @@ class Config extends Singleton * * ActiveRecord\Config::initialize(function($cfg) { * $cfg->set_model_directory('/your/app/models'); - * $cfg->set_connections(array( + * $cfg->set_connections([ * 'development' => 'mysql://user:pass@development.com/awesome_development', - * 'production' => 'mysql://user:pass@production.com/awesome_production')); + * 'production' => 'mysql://user:pass@production.com/awesome_production']); * }); * * @@ -52,7 +52,7 @@ class Config extends Singleton * * @var array */ - private $connections = array(); + private $connections = []; /** * Directory for the auto_loading of model classes. @@ -106,8 +106,8 @@ class Config extends Singleton * * ActiveRecord\Config::initialize(function($cfg) { * $cfg->set_model_directory('/path/to/your/model_directory'); - * $cfg->set_connections(array( - * 'development' => 'mysql://username:password@127.0.0.1/database_name')); + * $cfg->set_connections([ + * 'development' => 'mysql://username:password@127.0.0.1/database_name']); * }); * * @@ -116,8 +116,8 @@ class Config extends Singleton * * $cfg = ActiveRecord\Config::instance(); * $cfg->set_model_directory('/path/to/your/model_directory'); - * $cfg->set_connections(array('development' => - * 'mysql://username:password@localhost/database_name')); + * $cfg->set_connections(['development' => + * 'mysql://username:password@localhost/database_name']); * * * @param Closure $initializer A closure @@ -132,8 +132,8 @@ public static function initialize(Closure $initializer) * Sets the list of database connection strings. * * - * $config->set_connections(array( - * 'development' => 'mysql://username:password@127.0.0.1/database_name')); + * $config->set_connections([ + * 'development' => 'mysql://username:password@127.0.0.1/database_name']); * * * @param array $connections Array of connections @@ -363,13 +363,13 @@ public function set_date_format($format) * * * $config->set_cache("memcached://localhost"); - * $config->set_cache("memcached://localhost",array("expire" => 60)); + * $config->set_cache("memcached://localhost",["expire" => 60]); * * * @param string $url Url to your cache server. * @param array $options Array of options */ - public function set_cache($url, $options = array()) + public function set_cache($url, $options = []) { Cache::initialize($url, $options); } diff --git a/lib/Connection.php b/lib/Connection.php index ef0ae779a..d54901a4e 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -68,20 +68,26 @@ abstract class Connection public static $date_format = 'Y-m-d'; /** - * Database's datetime format + * Database's datetime format. + * + * Deliberately carries no time zone offset: MySQL before 8.0.19, MariaDB + * and SQLite do not understand one, and MySQL 8.0.19+ would convert the + * value to the session time zone, so a value would only round-trip when + * that matches PHP's. Plain wall-clock time behaves the same everywhere. + * * @var string */ - public static $datetime_format = 'Y-m-d H:i:sP'; + public static $datetime_format = 'Y-m-d H:i:s'; /** * Default PDO options to set for each connection. * @var array */ - public static $PDO_OPTIONS = array( + public static $PDO_OPTIONS = [ PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, - PDO::ATTR_STRINGIFY_FETCHES => false); + PDO::ATTR_STRINGIFY_FETCHES => false]; /** * The quote character for stuff like column and field names. @@ -157,12 +163,6 @@ private static function load_adapter_class($adapter) $class = ucwords($adapter) . 'Adapter'; $fqclass = 'ActiveRecord\\Adapters\\' . $class; - // $source = __DIR__ . "/adapters/$class.php"; - // if (!file_exists($source)) { - // throw new DatabaseException("$fqclass not found!"); - // } - // require_once($source); - return $fqclass; } @@ -243,7 +243,7 @@ public static function parse_connection_url($connection_url) if (isset($url['query'])) { foreach (explode('/&/', $url['query']) as $pair) { - list($name, $value) = explode('=', $pair); + [$name, $value] = explode('=', $pair); if ($name == 'charset') { $info->charset = $value; @@ -288,7 +288,7 @@ protected function __construct($info) */ public function columns($table) { - $columns = array(); + $columns = []; $sth = $this->query_column_info($table); while (($row = $sth->fetch())) { @@ -327,7 +327,7 @@ public function insert_id($sequence = null) * @param array &$values Optional array of bind values * @return mixed A result set object */ - public function query($sql, &$values = array()) + public function query($sql, &$values = []) { if ($this->logging) { $this->logger->log($sql); @@ -365,7 +365,7 @@ public function query($sql, &$values = array()) * @param array &$values Optional array of values to bind to the query. * @return string */ - public function query_and_fetch_one($sql, &$values = array()) + public function query_and_fetch_one($sql, &$values = []) { $sth = $this->query($sql, $values); $row = $sth->fetch(PDO::FETCH_NUM); @@ -394,7 +394,7 @@ public function query_and_fetch($sql, Closure $handler) */ public function tables() { - $tables = array(); + $tables = []; $sth = $this->query_for_tables(); while (($row = $sth->fetch(PDO::FETCH_NUM))) { diff --git a/lib/ConnectionManager.php b/lib/ConnectionManager.php index af522381a..f9c134a0f 100644 --- a/lib/ConnectionManager.php +++ b/lib/ConnectionManager.php @@ -17,7 +17,7 @@ class ConnectionManager extends Singleton * Array of {@link Connection} objects. * @var array */ - private static $connections = array(); + private static $connections = []; /** * If $name is null then the default connection will be returned. diff --git a/lib/DateTime.php b/lib/DateTime.php index 64b0b7829..22f32b0dc 100644 --- a/lib/DateTime.php +++ b/lib/DateTime.php @@ -45,7 +45,7 @@ class DateTime extends \DateTime implements DateTimeInterface /** * Pre-defined format strings. */ - public static $FORMATS = array( + public static $FORMATS = [ 'db' => 'Y-m-d H:i:s', 'number' => 'YmdHis', 'time' => 'H:i', @@ -61,7 +61,7 @@ class DateTime extends \DateTime implements DateTimeInterface 'rfc2822' => \DateTime::RFC2822, 'rfc3339' => \DateTime::RFC3339, 'rss' => \DateTime::RSS, - 'w3c' => \DateTime::W3C); + 'w3c' => \DateTime::W3C]; private $model; private $attribute_name; diff --git a/lib/Errors.php b/lib/Errors.php index 179e484e0..d2006d84d 100644 --- a/lib/Errors.php +++ b/lib/Errors.php @@ -16,7 +16,7 @@ class Errors implements IteratorAggregate private $model; private $errors; - public static $DEFAULT_ERROR_MESSAGES = array( + public static $DEFAULT_ERROR_MESSAGES = [ 'inclusion' => "is not included in the list", 'exclusion' => "is reserved", 'invalid' => "is invalid", @@ -37,7 +37,7 @@ class Errors implements IteratorAggregate 'unique' => "must be unique", 'less_than_or_equal_to' => "must be less than or equal to %d", 'greater_than_or_equal_to' => "must be greater than or equal to %d" - ); + ]; /** * Constructs an {@link Errors} object. @@ -72,7 +72,7 @@ public function add($attribute, $msg) } if (!isset($this->errors[$attribute])) { - $this->errors[$attribute] = array($msg); + $this->errors[$attribute] = [$msg]; } else { $this->errors[$attribute][] = $msg; } @@ -157,10 +157,10 @@ public function on($attribute) * * $model->errors->get_raw_errors(); * - * # array( - * # "name" => array("can't be blank"), - * # "state" => array("is the wrong length (should be 2 chars)", - * # ) + * # [ + * # "name" => ["can't be blank"], + * # "state" => ["is the wrong length (should be 2 chars)"], + * # ] * */ public function get_raw_errors() @@ -174,17 +174,17 @@ public function get_raw_errors() * * $model->errors->full_messages(); * - * # array( + * # [ * # "Name can't be blank", * # "State is the wrong length (should be 2 chars)" - * # ) + * # ] * * * @return array */ public function full_messages() { - $full_messages = array(); + $full_messages = []; $this->to_array(function ($attribute, $message) use (&$full_messages) { $full_messages[] = $message; @@ -199,10 +199,10 @@ public function full_messages() * * $model->errors->errors(); * - * # array( - * # "name" => array("Name can't be blank"), - * # "state" => array("State is the wrong length (should be 2 chars)") - * # ) + * # [ + * # "name" => ["Name can't be blank"], + * # "state" => ["State is the wrong length (should be 2 chars)"] + * # ] * * * @param callable $closure Closure to fetch the errors in some other format (optional) @@ -212,7 +212,7 @@ public function full_messages() */ public function to_array($closure = null) { - $errors = array(); + $errors = []; if ($this->errors) { foreach ($this->errors as $attribute => $messages) { @@ -262,7 +262,7 @@ public function is_empty() */ public function clear() { - $this->errors = array(); + $this->errors = []; } /** diff --git a/lib/Exceptions/DatabaseException.php b/lib/Exceptions/DatabaseException.php index 19defa505..8069a8891 100644 --- a/lib/Exceptions/DatabaseException.php +++ b/lib/Exceptions/DatabaseException.php @@ -21,12 +21,12 @@ public function __construct($adapter_or_string_or_mystery) { if ($adapter_or_string_or_mystery instanceof Connection) { parent::__construct( - join(", ", $adapter_or_string_or_mystery->connection->errorInfo()), + implode(", ", $adapter_or_string_or_mystery->connection->errorInfo()), intval($adapter_or_string_or_mystery->connection->errorCode()) ); } elseif ($adapter_or_string_or_mystery instanceof \PDOStatement) { parent::__construct( - join(", ", $adapter_or_string_or_mystery->errorInfo()), + implode(", ", $adapter_or_string_or_mystery->errorInfo()), intval($adapter_or_string_or_mystery->errorCode()) ); } else { diff --git a/lib/Expressions.php b/lib/Expressions.php index bf56784cb..f8a3c46a4 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -31,7 +31,7 @@ public function __construct($connection, $expressions = null /* [, $values ... ] $this->connection = $connection; if (is_array($expressions)) { $glue = func_num_args() > 2 ? func_get_arg(2) : ' AND '; - list($expressions,$values) = $this->build_sql_from_hash($expressions, $glue); + [$expressions,$values] = $this->build_sql_from_hash($expressions, $glue); } if ($expressions != '') { @@ -92,12 +92,12 @@ public function set_connection($connection) public function to_s($substitute = false, &$options = null) { if (!$options) { - $options = array(); + $options = []; } $values = array_key_exists('values', $options) ? $options['values'] : $this->values; $ret = ""; - $replace = array(); + $replace = []; $num_values = count($values); $len = strlen($this->expressions); $quotes = 0; @@ -124,6 +124,8 @@ public function to_s($substitute = false, &$options = null) private function build_sql_from_hash(&$hash, $glue) { $sql = $g = ""; + $values = []; + foreach ($hash as $name => $value) { if ($this->connection) { $name = $this->connection->quote_name($name); @@ -131,15 +133,18 @@ private function build_sql_from_hash(&$hash, $glue) if (is_array($value)) { $sql .= "$g$name IN(?)"; + $values[] = $value; } elseif (is_null($value)) { - $sql .= "$g$name IS ?"; + // A literal, not a bound NULL: PostgreSQL rejects "IS $1". + $sql .= "$g$name IS NULL"; } else { $sql .= "$g$name=?"; + $values[] = $value; } $g = $glue; } - return array($sql,array_values($hash)); + return [$sql, $values]; } private function substitute(&$values, $substitute, $pos, $parameter_index) @@ -164,7 +169,7 @@ private function substitute(&$values, $substitute, $pos, $parameter_index) return $ret; } - return join(',', array_fill(0, $value_count, self::PARAMETER_MARKER)); + return implode(',', array_fill(0, $value_count, self::PARAMETER_MARKER)); } if ($substitute) { diff --git a/lib/Inflector.php b/lib/Inflector.php index 6a2087deb..80d05d3ab 100644 --- a/lib/Inflector.php +++ b/lib/Inflector.php @@ -99,7 +99,7 @@ public function uncamelize($s) */ public function underscorify($s) { - return preg_replace(array('/[_\- ]+/','/([a-z])([A-Z])/'), array('_','\\1_\\2'), trim($s)); + return preg_replace(['/[_\- ]+/','/([a-z])([A-Z])/'], ['_','\\1_\\2'], trim($s)); } public function keyify($class_name) @@ -121,6 +121,6 @@ public function tableize($s) } public function variablize($s) { - return str_replace(array('-',' '), array('_','_'), strtolower(trim($s))); + return str_replace(['-',' '], ['_','_'], strtolower(trim($s))); } } diff --git a/lib/Model.php b/lib/Model.php index c5c01d7e7..85c605ba8 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -29,32 +29,32 @@ * * * class Person extends ActiveRecord\Model { - * static $belongs_to = array( - * array('parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person') - * ); + * static $belongs_to = [ + * ['parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person'] + * ]; * - * static $has_many = array( - * array('children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'), - * array('orders') - * ); + * static $has_many = [ + * ['children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'], + * ['orders'] + * ]; * - * static $validates_length_of = array( - * array('first_name', 'within' => array(1,50)), - * array('last_name', 'within' => array(1,50)) - * ); + * static $validates_length_of = [ + * ['first_name', 'within' => [1,50]], + * ['last_name', 'within' => [1,50]] + * ]; * } * * class Order extends ActiveRecord\Model { - * static $belongs_to = array( - * array('person') - * ); + * static $belongs_to = [ + * ['person'] + * ]; * - * static $validates_numericality_of = array( - * array('cost', 'greater_than' => 0), - * array('total', 'greater_than' => 0) - * ); + * static $validates_numericality_of = [ + * ['cost', 'greater_than' => 0], + * ['total', 'greater_than' => 0] + * ]; * - * static $before_save = array('calculate_total_with_tax'); + * static $before_save = ['calculate_total_with_tax']; * * public function calculate_total_with_tax() { * $this->total = $this->cost * 0.045; @@ -174,9 +174,9 @@ class Model * * * class Person extends ActiveRecord\Model { - * static $alias_attribute = array( + * static $alias_attribute = [ * 'alias_first_name' => 'first_name', - * 'alias_last_name' => 'last_name'); + * 'alias_last_name' => 'last_name']; * } * * $person = Person::first(); @@ -195,13 +195,13 @@ class Model * * * class Person extends ActiveRecord\Model { - * static $attr_accessible = array('first_name','last_name'); + * static $attr_accessible = ['first_name','last_name']; * } * - * $person = new Person(array( + * $person = new Person([ * 'first_name' => 'Tito', * 'last_name' => 'the Grief', - * 'id' => 11111)); + * 'id' => 11111]); * * echo $person->id; # => null * @@ -227,10 +227,10 @@ class Model * * * class Person extends ActiveRecord\Model { - * static $belongs_to = array(array('venue'),array('host')); - * static $delegate = array( - * array('name', 'state', 'to' => 'venue'), - * array('name', 'to' => 'host', 'prefix' => 'woot')); + * static $belongs_to = [['venue'],['host']]; + * static $delegate = [ + * ['name', 'state', 'to' => 'venue'], + * ['name', 'to' => 'host', 'prefix' => 'woot']]; * } * * @@ -254,7 +254,7 @@ class Model * $attributes will be mapped via set_attributes_via_mass_assignment. * * - * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief')); + * new Person(['first_name' => 'Tito', 'last_name' => 'the Grief']); * * * @param array $attributes Hash containing names and values to mass assign to the model @@ -263,7 +263,7 @@ class Model * @param boolean $new_record Set to true if this should be considered a new record * @return Model */ - public function __construct(array $attributes = array(), $guard_attributes = true, $instantiating_via_find = false, $new_record = true) + public function __construct(array $attributes = [], $guard_attributes = true, $instantiating_via_find = false, $new_record = true) { $this->__new_record = $new_record; @@ -279,7 +279,7 @@ public function __construct(array $attributes = array(), $guard_attributes = tru // since all attribute assignment now goes thru assign_attributes() we want to reset // dirty if instantiating via find since nothing is really dirty when doing that if ($instantiating_via_find) { - $this->__dirty = array(); + $this->__dirty = []; } $this->invoke_callback('after_construct', false); @@ -553,7 +553,7 @@ public function &read_attribute($name) public function flag_dirty($name) { if (!$this->__dirty) { - $this->__dirty = array(); + $this->__dirty = []; } $this->__dirty[$name] = true; @@ -631,22 +631,20 @@ public function get_real_attribute_name($name) * Will return an array looking like: * * - * array( - * 'name' => array( - * array('validator' => 'validates_presence_of'), - * array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')), - * 'password' => array( - * array('validator' => 'validates_length_of', 'minimum' => 6)) - * ) - * ); + * [ + * 'name' => [ + * ['validator' => 'validates_presence_of'], + * ['validator' => 'validates_inclusion_of', 'in' => ['Bob','Joe','John']], + * 'password' => [ + * ['validator' => 'validates_length_of', 'minimum' => 6]] + * ] + * ]; * * * @return array An array containing validator data for this model. */ public function get_validation_rules() { - require_once 'Validations.php'; - $validator = new Validations($this); return $validator->rules(); } @@ -659,7 +657,7 @@ public function get_validation_rules() */ public function get_values_for($attributes) { - $ret = array(); + $ret = []; foreach ($attributes as $name) { if (array_key_exists($name, $this->attributes)) { @@ -834,20 +832,13 @@ private function insert($validate = true) $use_sequence = false; if ($table->sequence && !isset($attributes[$pk])) { - if (($conn = static::connection()) instanceof OciAdapter) { - // terrible oracle makes us select the nextval first - $attributes[$pk] = $conn->get_next_sequence_value($table->sequence); - $table->insert($attributes); - $this->attributes[$pk] = $attributes[$pk]; - } else { - // unset pk that was set to null - if (array_key_exists($pk, $attributes)) { - unset($attributes[$pk]); - } - - $table->insert($attributes, $pk, $table->sequence); - $use_sequence = true; + // unset pk that was set to null + if (array_key_exists($pk, $attributes)) { + unset($attributes[$pk]); } + + $table->insert($attributes, $pk, $table->sequence); + $use_sequence = true; } else { $table->insert($attributes); } @@ -926,19 +917,19 @@ protected function cache_key() * Delete all using a hash: * * - * YourModel::delete_all(array('conditions' => array('name' => 'Tito'))); + * YourModel::delete_all(['conditions' => ['name' => 'Tito']]); * * * Delete all using an array: * * - * YourModel::delete_all(array('conditions' => array('name = ?', 'Tito'))); + * YourModel::delete_all(['conditions' => ['name = ?', 'Tito']]); * * * Delete all using a string: * * - * YourModel::delete_all(array('conditions' => 'name = "Tito"')); + * YourModel::delete_all(['conditions' => 'name = "Tito"']); * * * An options array takes the following parameters: @@ -952,7 +943,7 @@ protected function cache_key() * @param array $options * @return integer Number of rows affected */ - public static function delete_all($options = array()) + public static function delete_all($options = []) { $table = static::table(); $conn = static::connection(); @@ -961,7 +952,7 @@ public static function delete_all($options = array()) $conditions = is_array($options) ? $options['conditions'] : $options; if (is_array($conditions) && !Utils::is_hash($conditions)) { - call_user_func_array(array($sql, 'delete'), $conditions); + call_user_func_array([$sql, 'delete'], $conditions); } else { $sql->delete($conditions); } @@ -987,13 +978,13 @@ public static function delete_all($options = array()) * Update all using a hash: * * - * YourModel::update_all(array('set' => array('name' => "Bob"))); + * YourModel::update_all(['set' => ['name' => "Bob"]]); * * * Update all using a string: * * - * YourModel::update_all(array('set' => 'name = "Bob"')); + * YourModel::update_all(['set' => 'name = "Bob"']); * * * An options array takes the following parameters: @@ -1008,7 +999,7 @@ public static function delete_all($options = array()) * @param array $options * @return integer Number of rows affected */ - public static function update_all($options = array()) + public static function update_all($options = []) { $table = static::table(); $conn = static::connection(); @@ -1018,7 +1009,7 @@ public static function update_all($options = array()) if (isset($options['conditions']) && ($conditions = $options['conditions'])) { if (is_array($conditions) && !Utils::is_hash($conditions)) { - call_user_func_array(array($sql, 'where'), $conditions); + call_user_func_array([$sql, 'where'], $conditions); } else { $sql->where($conditions); } @@ -1066,7 +1057,7 @@ public function delete() /** * Helper that creates an array of values for the primary key(s). * - * @return array An array in the form array(key_name => value, ...) + * @return array An array in the form [key_name => value, ...] */ public function values_for_pk() { @@ -1077,11 +1068,11 @@ public function values_for_pk() * Helper to return a hash of values for the specified attributes. * * @param array $attribute_names Array of attribute names - * @return array An array in the form array(name => value, ...) + * @return array An array in the form [name => value, ...] */ public function values_for($attribute_names) { - $filter = array(); + $filter = []; foreach ($attribute_names as $name) { $filter[$name] = $this->$name; @@ -1097,12 +1088,10 @@ public function values_for($attribute_names) */ private function validate() { - require_once 'Validations.php'; - $validator = new Validations($this); $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update'); - foreach (array('before_validation', "before_$validation_on") as $callback) { + foreach (['before_validation', "before_$validation_on"] as $callback) { if (!$this->invoke_callback($callback, false)) { return false; } @@ -1112,7 +1101,7 @@ private function validate() $this->errors = $validator->get_record(); $validator->validate(); - foreach (array('after_validation', "after_$validation_on") as $callback) { + foreach (['after_validation', "after_$validation_on"] as $callback) { $this->invoke_callback($callback, false); } @@ -1174,7 +1163,7 @@ public function set_timestamps() /** * Mass update the model with an array of attribute data and saves to the database. * - * @param array $attributes An attribute data array in the form array(name => value, ...) + * @param array $attributes An attribute data array in the form [name => value, ...] * @return boolean True if successfully updated and saved otherwise false */ public function update_attributes($attributes) @@ -1203,7 +1192,7 @@ public function update_attribute($name, $value) * but DOES NOT save it to the database. * * @see update_attributes - * @param array $attributes An array containing data to update in the form array(name => value, ...) + * @param array $attributes An array containing data to update in the form [name => value, ...] */ public function set_attributes(array $attributes) { @@ -1214,14 +1203,14 @@ public function set_attributes(array $attributes) * Passing $guard_attributes as true will throw an exception if an attribute does not exist. * * @throws \ActiveRecord\UndefinedPropertyException - * @param array $attributes An array in the form array(name => value, ...) + * @param array $attributes An array in the form [name => value, ...] * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded */ private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes) { //access uninflected columns since that is what we would have in result set $table = static::table(); - $exceptions = array(); + $exceptions = []; $use_attr_accessible = !empty(static::$attr_accessible); $use_attr_protected = !empty(static::$attr_protected); $connection = static::connection(); @@ -1249,11 +1238,6 @@ private function set_attributes_via_mass_assignment(array &$attributes, $guard_a $exceptions[] = $e->getMessage(); } } else { - // ignore OciAdapter's limit() stuff - if ($name == 'ar_rnum__') { - continue; - } - // set arbitrary data $this->assign_attribute($name, $value); } @@ -1284,7 +1268,7 @@ public function set_relationship_from_eager_load(?Model $model = null, ?string $ if ($rel->is_poly()) { // if the related model is null and it is a poly then we should have an empty array if (is_null($model)) { - return $this->__relationships[$name] = array(); + return $this->__relationships[$name] = []; } else { return $this->__relationships[$name][] = $model; } @@ -1303,7 +1287,7 @@ public function set_relationship_from_eager_load(?Model $model = null, ?string $ */ public function reload() { - $this->__relationships = array(); + $this->__relationships = []; $pk = array_values($this->get_values_for($this->get_primary_key())); $this->expire_cache(); @@ -1315,7 +1299,7 @@ public function reload() public function __clone() { - $this->__relationships = array(); + $this->__relationships = []; $this->reset_dirty(); return $this; } @@ -1360,14 +1344,14 @@ public function reset_dirty() * * # would be the equivalent of * if (!Person::find_by_name('Tito')) - * Person::create(array('Tito')); + * Person::create(['Tito']); * * * Some other examples of find_or_create_by: * * * Person::find_or_create_by_name_and_id('Tito',1); - * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1)); + * Person::find_or_create_by_name_and_id(['name' => 'Tito', 'id' => 1]); * * * @param string $method Name of method @@ -1428,7 +1412,7 @@ public function __call($method, $args) $args = $args[0]; } - $association_name = str_replace(array('build_', 'create_'), '', $method); + $association_name = str_replace(['build_', 'create_'], '', $method); $method = str_replace($association_name, 'association', $method); $table = static::table(); @@ -1461,7 +1445,7 @@ public static function all(/* ... */) * Get a count of qualifying records. * * - * YourModel::count(array('conditions' => 'amount > 3.14159265')); + * YourModel::count(['conditions' => 'amount > 3.14159265']); * * * @see find @@ -1492,8 +1476,8 @@ public static function count(/* ... */) * * * SomeModel::exists(123); - * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito'))); - * SomeModel::exists(array('id' => 123, 'name' => 'Tito')); + * SomeModel::exists(['conditions' => ['id=? and name=?', 123, 'Tito']]); + * SomeModel::exists(['id' => 123, 'name' => 'Tito']); * * * @see find @@ -1539,24 +1523,24 @@ public static function last(/* ... */) * YourModel::find(1,2,3); * * # finding by pk accepts an options array - * YourModel::find(123,array('order' => 'name desc')); + * YourModel::find(123,['order' => 'name desc']); * * * Finding by using a conditions array: * * - * YourModel::find('first', array('conditions' => array('name=?','Tito'), - * 'order' => 'name asc')) - * YourModel::find('all', array('conditions' => 'amount > 3.14159265')); - * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3)))); + * YourModel::find('first', ['conditions' => ['name=?','Tito'], + * 'order' => 'name asc']) + * YourModel::find('all', ['conditions' => 'amount > 3.14159265']); + * YourModel::find('all', ['conditions' => ['id in(?)', [1,2,3]]]); * * * Finding by using a hash: * * - * YourModel::find(array('name' => 'Tito', 'id' => 1)); - * YourModel::find('first',array('name' => 'Tito', 'id' => 1)); - * YourModel::find('all',array('name' => 'Tito', 'id' => 1)); + * YourModel::find(['name' => 'Tito', 'id' => 1]); + * YourModel::find('first',['name' => 'Tito', 'id' => 1]); + * YourModel::find('all',['name' => 'Tito', 'id' => 1]); * * * An options array can take the following parameters: @@ -1565,8 +1549,8 @@ public static function last(/* ... */) *
  • select: A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'
  • *
  • joins: A SQL join fragment such as: 'JOIN roles ON(roles.user_id=user.id)' or a named association on the model
  • *
  • include: TODO not implemented yet
  • - *
  • conditions: A SQL fragment such as: 'id=1', array('id=1'), array('name=? and id=?','Tito',1), array('name IN(?)', array('Tito','Bob')), - * array('name' => 'Tito', 'id' => 1)
  • + *
  • conditions: A SQL fragment such as: 'id=1', ['id=1'], ['name=? and id=?','Tito',1], ['name IN(?)', ['Tito','Bob']], + * ['name' => 'Tito', 'id' => 1]
  • *
  • limit: Number of records to limit the query to
  • *
  • offset: The row offset to return results from for the query
  • *
  • order: A SQL fragment for order such as: 'name asc', 'name asc, id desc'
  • @@ -1601,7 +1585,7 @@ public static function find(/* $type, $options */) case 'last': if (!array_key_exists('order', $options)) { - $options['order'] = join(' DESC, ', static::table()->pk) . ' DESC'; + $options['order'] = implode(' DESC, ', static::table()->pk) . ' DESC'; } else { $options['order'] = SQLBuilder::reverse_order($options['order']); } @@ -1640,11 +1624,11 @@ public static function find(/* $type, $options */) */ protected static function get_models_from_cache($pks, $options) { - $models = array(); + $models = []; $table = static::table(); if (!is_array($pks)) { - $pks = array($pks); + $pks = [$pks]; } foreach ($pks as $pk) { @@ -1674,6 +1658,17 @@ public static function find_by_pk($values, $options) $table = static::table(); + // A non-numeric value cannot match an integer key. MySQL silently coerces + // it to 0; PostgreSQL rejects the bound value with a type error. + $pk_column = $table->columns[$table->pk[0]] ?? null; + if ($pk_column && $pk_column->type == Column::INTEGER) { + foreach ((array)$values as $value) { + if (!is_numeric($value)) { + throw new Exceptions\RecordNotFound("Couldn't find " . get_called_class() . " with ID=$value"); + } + } + } + if ($table->cache_individual_model) { $list = static::get_models_from_cache($values, $options); } else { @@ -1685,7 +1680,7 @@ public static function find_by_pk($values, $options) if ($results != ($expected = count((array)$values))) { $class = get_called_class(); if (is_array($values)) { - $values = join(',', $values); + $values = implode(',', $values); } if ($expected == 1) { @@ -1701,7 +1696,7 @@ public static function find_by_pk($values, $options) * Find using a raw SELECT query. * * - * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito')); + * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",['Tito']); * YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'"); * * @@ -1741,7 +1736,7 @@ public static function is_options_hash($array, $throw = true) $diff = array_diff($keys, self::$VALID_OPTIONS); if (!empty($diff) && $throw) { - throw new Exceptions\ActiveRecordException("Unknown key(s): " . join(', ', $diff)); + throw new Exceptions\ActiveRecordException("Unknown key(s): " . implode(', ', $diff)); } $intersect = array_intersect($keys, self::$VALID_OPTIONS); @@ -1758,12 +1753,12 @@ public static function is_options_hash($array, $throw = true) * * @internal This needs to eventually support composite keys. * @param mixed $args Primary key value(s) - * @return array An array in the form array(name => value, ...) + * @return array An array in the form [name => value, ...] */ public static function pk_conditions($args) { $table = static::table(); - $ret = array($table->pk[0] => $args); + $ret = [$table->pk[0] => $args]; return $ret; } @@ -1776,7 +1771,7 @@ public static function pk_conditions($args) */ public static function extract_and_validate_options(array &$array) { - $options = array(); + $options = []; if ($array) { $last = &$array[count($array) - 1]; @@ -1791,7 +1786,7 @@ public static function extract_and_validate_options(array &$array) throw $e; } - $options = array('conditions' => $last); + $options = ['conditions' => $last]; } } return $options; @@ -1804,7 +1799,7 @@ public static function extract_and_validate_options(array &$array) * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options) * @return string JSON representation of the model */ - public function to_json(array $options = array()) + public function to_json(array $options = []) { return $this->serialize('Json', $options); } @@ -1816,7 +1811,7 @@ public function to_json(array $options = array()) * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options) * @return string XML representation of the model */ - public function to_xml(array $options = array()) + public function to_xml(array $options = []) { return $this->serialize('Xml', $options); } @@ -1830,10 +1825,10 @@ public function to_xml(array $options = array()) * * ActiveRecord\Serializers\CsvSerializer::$delimiter=';'; * ActiveRecord\Serializers\CsvSerializer::$enclosure=''; - * YourModel::find('first')->to_csv(array('only'=>array('name','level'))); + * YourModel::find('first')->to_csv(['only'=>['name','level']]); * returns: Joe,2 * - * YourModel::find('first')->to_csv(array('only_header'=>true,'only'=>array('name','level'))); + * YourModel::find('first')->to_csv(['only_header'=>true,'only'=>['name','level']]); * returns: name,level * * @@ -1841,7 +1836,7 @@ public function to_xml(array $options = array()) * @param array $options An array containing options for csv serialization (see {@link Serialization} for valid options) * @return string CSV representation of the model */ - public function to_csv(array $options = array()) + public function to_csv(array $options = []) { return $this->serialize('Csv', $options); } @@ -1853,7 +1848,7 @@ public function to_csv(array $options = array()) * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options) * @return array Array representation of the model */ - public function to_array(array $options = array()) + public function to_array(array $options = []) { return $this->serialize('Array', $options); } @@ -1900,7 +1895,7 @@ private function invoke_callback($method_name, $must_exist = true) * * YourModel::transaction(function() * { - * YourModel::create(array("name" => "blah")); + * YourModel::create(["name" => "blah"]); * }); * * @@ -1911,13 +1906,13 @@ private function invoke_callback($method_name, $must_exist = true) * * YourModel::transaction(function() * { - * YourModel::create(array("name" => "blah")); + * YourModel::create(["name" => "blah"]); * throw new Exception("rollback!"); * }); * * YourModel::transaction(function() * { - * YourModel::create(array("name" => "blah")); + * YourModel::create(["name" => "blah"]); * return false; # rollback! * }); * diff --git a/lib/Reflections.php b/lib/Reflections.php index 8d5c976a0..b86529841 100644 --- a/lib/Reflections.php +++ b/lib/Reflections.php @@ -7,6 +7,7 @@ namespace ActiveRecord; use ReflectionClass; +use ActiveRecord\Exceptions\ActiveRecordException; /** * Simple class that caches reflections of classes. @@ -20,7 +21,7 @@ class Reflections extends Singleton * * @var array */ - private $reflections = array(); + private $reflections = []; /** * Instantiates a new ReflectionClass for the given class. @@ -96,7 +97,7 @@ private function get_class($mixed = null) if (class_exists($mixed)) { return $mixed; } else { - return join('\\', array_filter([Config::instance()->get_model_namespace(), $mixed])); + return implode('\\', array_filter([Config::instance()->get_model_namespace(), $mixed])); } } diff --git a/lib/Relationships/HasMany.php b/lib/Relationships/HasMany.php index b91c991b2..ad3a7e2ee 100644 --- a/lib/Relationships/HasMany.php +++ b/lib/Relationships/HasMany.php @@ -24,9 +24,9 @@ * # Table: schools * # Primary key: id * class School extends ActiveRecord\Model { - * static $has_many = array( - * array('people') - * ); + * static $has_many = [ + * ['people'] + * ]; * }); * * @@ -34,19 +34,19 @@ * * * class Payment extends ActiveRecord\Model { - * static $belongs_to = array( - * array('person'), - * array('order') - * ); + * static $belongs_to = [ + * ['person'], + * ['order'] + * ]; * } * * class Order extends ActiveRecord\Model { - * static $has_many = array( - * array('people', + * static $has_many = [ + * ['people', * 'through' => 'payments', * 'select' => 'people.*, payments.amount', - * 'conditions' => 'payments.amount < 200') - * ); + * 'conditions' => 'payments.amount < 200'] + * ]; * } * * @@ -69,7 +69,7 @@ class HasMany extends AbstractRelationship * * @var array */ - protected static $valid_association_options = array('primary_key', 'order', 'group', 'having', 'limit', 'offset', 'through', 'source'); + protected static $valid_association_options = ['primary_key', 'order', 'group', 'having', 'limit', 'offset', 'through', 'source']; protected $initialized; protected $primary_key; @@ -83,7 +83,7 @@ class HasMany extends AbstractRelationship * @param array $options Options for the association * @return HasMany */ - public function __construct($options = array()) + public function __construct($options = []) { parent::__construct($options); @@ -96,7 +96,7 @@ public function __construct($options = array()) } if (!$this->primary_key && isset($this->options['primary_key'])) { - $this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : array($this->options['primary_key']); + $this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : [$this->options['primary_key']]; } if (!$this->class_name) { @@ -108,7 +108,7 @@ protected function set_keys($model_class_name, $override = false) { //infer from class_name if (!$this->foreign_key || $override) { - $this->foreign_key = array(Inflector::instance()->keyify($model_class_name)); + $this->foreign_key = [Inflector::instance()->keyify($model_class_name)]; } if (!$this->primary_key || $override) { @@ -175,9 +175,9 @@ private function get_foreign_key_for_new_association(Model $model) $this->set_keys($model); $primary_key = Inflector::instance()->variablize($this->foreign_key[0]); - return array( + return [ $primary_key => $model->id, - ); + ]; } private function inject_foreign_key_for_new_association(Model $model, &$attributes) @@ -191,7 +191,7 @@ private function inject_foreign_key_for_new_association(Model $model, &$attribut return $attributes; } - public function build_association(Model $model, $attributes = array(), $guard_attributes = true) + public function build_association(Model $model, $attributes = [], $guard_attributes = true) { $relationship_attributes = $this->get_foreign_key_for_new_association($model); @@ -212,7 +212,7 @@ public function build_association(Model $model, $attributes = array(), $guard_at return $record; } - public function create_association(Model $model, $attributes = array(), $guard_attributes = true) + public function create_association(Model $model, $attributes = [], $guard_attributes = true) { $relationship_attributes = $this->get_foreign_key_for_new_association($model); @@ -236,7 +236,7 @@ public function create_association(Model $model, $attributes = array(), $guard_a return $record; } - public function load_eagerly($models = array(), $attributes = array(), $includes = null, ?Table $table = null) + public function load_eagerly($models = [], $attributes = [], $includes = null, ?Table $table = null) { $this->set_keys($table->class->name); $this->query_and_attach_related_models_eagerly($table, $models, $attributes, $includes, $this->foreign_key, $table->pk); diff --git a/lib/Relationships/HasOne.php b/lib/Relationships/HasOne.php index c079a43b0..8b8a71baa 100644 --- a/lib/Relationships/HasOne.php +++ b/lib/Relationships/HasOne.php @@ -17,7 +17,7 @@ * # Table name: people * # Foreign key: state_id * class Person extends ActiveRecord\Model { - * static $has_one = array(array('state')); + * static $has_one = [['state']]; * } * * diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index 24f680670..6d6048638 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -209,18 +209,18 @@ public static function reverse_order($order) $parts[$i] .= ' DESC'; } } - return join(',', $parts); + return implode(',', $parts); } /** - * Converts a string like "id_and_name_or_z" into a conditions value like array("id=? AND name=? OR z=?", values, ...). + * Converts a string like "id_and_name_or_z" into a conditions value like ["id=? AND name=? OR z=?", values, ...]. * * @param Connection $connection * @param $name Underscored string * @param $values Array of values for the field names. This is used * to determine what kind of bind marker to use: =?, IN(?), IS NULL * @param $map A hash of "mapped_column_name" => "real_column_name" - * @return A conditions array in the form array(sql_string, value1, value2,...) + * @return A conditions array in the form [sql_string, value1, value2,...] */ public static function create_conditions_from_underscored_string(Connection $connection, $name, &$values = [], &$map = null) { @@ -262,7 +262,7 @@ public static function create_conditions_from_underscored_string(Connection $con * @param string $name A string containing attribute names connected with _and_ or _or_ * @param $args Array of values for each attribute in $name * @param $map A hash of "mapped_column_name" => "real_column_name" - * @return array A hash of array(name => value, ...) + * @return array A hash of [name => value, ...] */ public static function create_hash_from_underscored_string($name, &$values = [], &$map = null) { @@ -299,7 +299,6 @@ private function prepend_table_name_to_fields($hash = []) private function apply_where_conditions($args) { - require_once 'Expressions.php'; $num_args = count($args); if ($num_args == 1 && Utils::is_hash($args[0])) { @@ -350,8 +349,7 @@ private function build_delete() private function build_insert() { - require_once 'Expressions.php'; - $keys = join(',', $this->quoted_key_names()); + $keys = implode(',', $this->quoted_key_names()); if ($this->sequence) { $sql = @@ -401,7 +399,7 @@ private function build_update() if (!is_null($this->update) && (strlen($this->update) > 0)) { $set = $this->update; } else { - $set = join('=?, ', $this->quoted_key_names()) . '=?'; + $set = implode('=?, ', $this->quoted_key_names()) . '=?'; } $sql = "UPDATE $this->table SET $set"; diff --git a/lib/Serialization.php b/lib/Serialization.php index c8eeb92b7..264e68c48 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -6,6 +6,8 @@ namespace ActiveRecord; +use ActiveRecord\Exceptions\UndefinedPropertyException; + /** * Base class for Model serializers. * @@ -28,14 +30,14 @@ * # run $model->encoded_description() and include its return value * # include the comments association * # include posts association with its own options (nested) - * $model->to_json(array( - * 'only' => array('id','name', 'encoded_description'), - * 'methods' => array('encoded_description'), - * 'include' => array('comments', 'posts' => array('only' => 'id')) - * )); + * $model->to_json([ + * 'only' => ['id','name', 'encoded_description'], + * 'methods' => ['encoded_description'], + * 'include' => ['comments', 'posts' => ['only' => 'id']] + * ]); * * # except the password field from being included - * $model->to_xml(array('except' => 'password'))); + * $model->to_xml(['except' => 'password'])); * * * @package ActiveRecord @@ -62,25 +64,25 @@ abstract class Serialization * the include option was used: * * - * $user = array('id' => 1, 'name' => 'Tito', - * 'permissions' => array( - * 'permission' => array( - * array('id' => 100, 'name' => 'admin'), - * array('id' => 101, 'name' => 'normal') - * ) - * ) - * ); + * $user = ['id' => 1, 'name' => 'Tito', + * 'permissions' => [ + * 'permission' => [ + * ['id' => 100, 'name' => 'admin'], + * ['id' => 101, 'name' => 'normal'] + * ] + * ] + * ]; * * * Setting to false will produce this: * * - * $user = array('id' => 1, 'name' => 'Tito', - * 'permissions' => array( - * array('id' => 100, 'name' => 'admin'), - * array('id' => 101, 'name' => 'normal') - * ) - * ); + * $user = ['id' => 1, 'name' => 'Tito', + * 'permissions' => [ + * ['id' => 100, 'name' => 'admin'], + * ['id' => 101, 'name' => 'normal'] + * ] + * ]; * * * @var boolean @@ -162,7 +164,7 @@ private function check_include() foreach ($this->options['include'] as $association => $options) { if (!is_array($options)) { $association = $options; - $options = array(); + $options = []; } try { @@ -175,7 +177,7 @@ private function check_include() $this->attributes[$association] = $serialized->to_a(); ; } else { - $includes = array(); + $includes = []; foreach ($assoc as $a) { $serialized = new $serializer_class($a, $options); @@ -199,7 +201,7 @@ private function check_include() final protected function options_to_a($key) { if (!is_array($this->options[$key])) { - $this->options[$key] = array($this->options[$key]); + $this->options[$key] = [$this->options[$key]]; } } diff --git a/lib/Singleton.php b/lib/Singleton.php index d94897778..c210163a9 100644 --- a/lib/Singleton.php +++ b/lib/Singleton.php @@ -21,7 +21,7 @@ abstract class Singleton * * @var array */ - private static $instances = array(); + private static $instances = []; /** * Static method for instantiating a singleton object. diff --git a/lib/Table.php b/lib/Table.php index f05748c2c..592d1e7b0 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -7,7 +7,8 @@ namespace ActiveRecord; use ActiveRecord\Exceptions\RelationshipException; - +use ActiveRecord\Adapters\PgsqlAdapter; + /** * Manages reading and writing to a database table. * @@ -88,7 +89,7 @@ public static function clear_cache($model_class_name = null) if ($model_class_name && array_key_exists($model_class_name, self::$cache)) { unset(self::$cache[$model_class_name]); } else { - self::$cache = array(); + self::$cache = []; } } @@ -108,10 +109,10 @@ public function __construct($class_name) $this->callback = new CallBack($class_name); $this->callback->register('before_save', function (Model $model) { $model->set_timestamps(); - }, array('prepend' => true)); + }, ['prepend' => true]); $this->callback->register('after_save', function (Model $model) { $model->reset_dirty(); - }, array('prepend' => true)); + }, ['prepend' => true]); } public function reestablish_connection($close = true) @@ -185,16 +186,16 @@ public function options_to_sql($options) if (array_key_exists('conditions', $options)) { if (!Utils::is_hash($options['conditions'])) { if (is_string($options['conditions'])) { - $options['conditions'] = array($options['conditions']); + $options['conditions'] = [$options['conditions']]; } - call_user_func_array(array($sql,'where'), $options['conditions']); + call_user_func_array([$sql,'where'], $options['conditions']); } else { if (!empty($options['mapped_names'])) { $options['conditions'] = $this->map_names($options['conditions'], $options['mapped_names']); } - $sql->where($options['conditions']); + $sql->where($this->map_column_names($options['conditions'])); } } @@ -243,7 +244,7 @@ public function find_by_sql($sql, $values = null, $readonly = false, $includes = $this->last_sql = $sql; $collect_attrs_for_includes = is_null($includes) ? false : true; - $list = $attrs = array(); + $list = $attrs = []; $sth = $this->conn->query($sql, $this->process_data($values)); @@ -285,19 +286,19 @@ public function find_by_sql($sql, $values = null, $readonly = false, $includes = * @param $includes array eager load directives * @return void */ - private function execute_eager_load($models = array(), $attrs = array(), $includes = array()) + private function execute_eager_load($models = [], $attrs = [], $includes = []) { if (!is_array($includes)) { - $includes = array($includes); + $includes = [$includes]; } foreach ($includes as $index => $name) { // nested include if (is_array($name)) { - $nested_includes = count($name) > 0 ? $name : array(); + $nested_includes = count($name) > 0 ? $name : []; $name = $index; } else { - $nested_includes = array(); + $nested_includes = []; } $rel = $this->get_relationship($name, true); @@ -315,6 +316,24 @@ public function get_column_by_inflected_name($inflected_name) return null; } + /** + * Returns the real column name for an attribute name, resolving inflected + * names such as some_date for a some_Date column. Databases that preserve + * identifier case, like PostgreSQL, reject the inflected name once quoted. + * + * @param string $name Attribute or column name + * @return string + */ + public function column_name_for($name) + { + if (isset($this->columns[$name])) { + return $name; + } + + $column = $this->get_column_by_inflected_name($name); + return $column ? $column->name : $name; + } + public function get_fully_qualified_table_name($quote_name = true) { $table = $quote_name ? $this->conn->quote_name($this->table) : $this->table; @@ -424,7 +443,7 @@ private function get_meta_data() */ private function map_names(&$hash, &$map) { - $ret = array(); + $ret = []; foreach ($hash as $name => &$value) { if (array_key_exists($name, $map)) { @@ -436,6 +455,17 @@ private function map_names(&$hash, &$map) return $ret; } + private function map_column_names($hash) + { + $ret = []; + + foreach ($hash as $name => $value) { + $ret[$this->column_name_for($name)] = $value; + } + + return $ret; + } + private function &process_data($hash) { if (!$hash) { @@ -443,26 +473,31 @@ private function &process_data($hash) } $date_class = Config::instance()->get_date_class(); - foreach ($hash as $name => &$value) { + $ret = []; + + foreach ($hash as $name => $value) { + $name = $this->column_name_for($name); + if ($value instanceof $date_class || $value instanceof \DateTime) { if (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE) { - $hash[$name] = $this->conn->date_to_string($value); + $value = $this->conn->date_to_string($value); } else { - $hash[$name] = $this->conn->datetime_to_string($value); + $value = $this->conn->datetime_to_string($value); } - } else { - $hash[$name] = $value; } + + $ret[$name] = $value; } - return $hash; + + return $ret; } private function set_primary_key() { if (($pk = $this->class->getStaticPropertyValue('pk', null)) || ($pk = $this->class->getStaticPropertyValue('primary_key', null))) { - $this->pk = is_array($pk) ? $pk : array($pk); + $this->pk = is_array($pk) ? $pk : [$pk]; } else { - $this->pk = array(); + $this->pk = []; foreach ($this->columns as $c) { if ($c->pk) { @@ -518,7 +553,6 @@ private function set_sequence_name() private function set_associations() { - // require_once __DIR__ . '/Relationship.php'; $namespace = $this->class->getNamespaceName(); foreach ($this->class->getStaticProperties() as $name => $definitions) { @@ -532,7 +566,7 @@ private function set_associations() foreach (Utils::wrap_strings_in_arrays($definitions) as $definition) { $relationship = null; - $definition += array('namespace' => $namespace); + $definition += ['namespace' => $namespace]; switch ($name) { case 'has_many': @@ -563,14 +597,14 @@ private function set_associations() * Rebuild the delegates array into format that we can more easily work with in Model. * Will end up consisting of array of: * - * array('delegate' => array('field1','field2',...), + * ['delegate' => ['field1','field2',...], * 'to' => 'delegate_to_relationship', - * 'prefix' => 'prefix') + * 'prefix' => 'prefix'] */ private function set_delegates() { - $delegates = $this->class->getStaticPropertyValue('delegate', array()); - $new = array(); + $delegates = $this->class->getStaticPropertyValue('delegate', []); + $new = []; if (!array_key_exists('processed', $delegates)) { $delegates['processed'] = false; @@ -586,10 +620,10 @@ private function set_delegates() $delegate['prefix'] = null; } - $new_delegate = array( + $new_delegate = [ 'to' => $delegate['to'], 'prefix' => $delegate['prefix'], - 'delegate' => array()); + 'delegate' => []]; foreach ($delegate as $name => $value) { if (is_numeric($name)) { @@ -610,8 +644,8 @@ private function set_delegates() */ private function set_setters_and_getters() { - $getters = $this->class->getStaticPropertyValue('getters', array()); - $setters = $this->class->getStaticPropertyValue('setters', array()); + $getters = $this->class->getStaticPropertyValue('getters', []); + $setters = $this->class->getStaticPropertyValue('setters', []); if (!empty($getters) || !empty($setters)) { trigger_error('static::$getters and static::$setters are deprecated. Please define your setters and getters by declaring methods in your model prefixed with get_ or set_. See diff --git a/lib/Utils.php b/lib/Utils.php index 4a537ed0c..c05977796 100644 --- a/lib/Utils.php +++ b/lib/Utils.php @@ -144,7 +144,7 @@ public static function all($needle, array $haystack) public static function collect(&$enumerable, $name_or_closure) { - $ret = array(); + $ret = []; foreach ($enumerable as $value) { if (is_string($name_or_closure)) { @@ -176,10 +176,10 @@ public static function wrap_strings_in_arrays(&$strings) public static function extract_options($options) { - return is_array(end($options)) ? end($options) : array(); + return is_array(end($options)) ? end($options) : []; } - public static function add_condition(&$conditions = array(), $condition = null, $conjuction = 'AND') + public static function add_condition(&$conditions = [], $condition = null, $conjuction = 'AND') { if (is_array($condition)) { if (empty($conditions)) { @@ -226,7 +226,7 @@ public static function is_blank($var) return empty($var) || (strlen($var) == 0); } - private static $plural = array( + private static $plural = [ '/(quiz)$/i' => "$1zes", '/^(ox)$/i' => "$1en", '/([m|l])ouse$/i' => "$1ice", @@ -246,9 +246,9 @@ public static function is_blank($var) '/(us)$/i' => "$1es", '/s$/i' => "s", '/$/' => "s" - ); + ]; - private static $singular = array( + private static $singular = [ '/(quiz)zes$/i' => "$1", '/(matr)ices$/i' => "$1ix", '/(vert|ind)ices$/i' => "$1ex", @@ -278,9 +278,9 @@ public static function is_blank($var) '/(us)es$/i' => "$1", '/(us|ss)$/i' => "$1", '/s$/i' => "" - ); + ]; - private static $irregular = array( + private static $irregular = [ 'move' => 'moves', 'foot' => 'feet', 'goose' => 'geese', @@ -289,9 +289,9 @@ public static function is_blank($var) 'man' => 'men', 'tooth' => 'teeth', 'person' => 'people' - ); + ]; - private static $uncountable = array( + private static $uncountable = [ 'sheep', 'fish', 'deer', @@ -301,7 +301,7 @@ public static function is_blank($var) 'rice', 'information', 'equipment' - ); + ]; public static function pluralize($string) { diff --git a/lib/Validations.php b/lib/Validations.php index 96e801403..5751818d5 100644 --- a/lib/Validations.php +++ b/lib/Validations.php @@ -21,10 +21,10 @@ * * * class Person extends ActiveRecord\Model { - * static $validates_length_of = array( - * array('name', 'within' => array(30,100), - * array('state', 'is' => 2) - * ); + * static $validates_length_of = [ + * ['name', 'within' => [30,100]], + * ['state', 'is' => 2] + * ]; * } * * $person = new Person(); @@ -44,10 +44,10 @@ class Validations private $model; private $record; private $klass; - private $options = array(); - private $validators = array(); + private $options = []; + private $validators = []; - private static $VALIDATION_FUNCTIONS = array( + private static $VALIDATION_FUNCTIONS = [ 'validates_presence_of', 'validates_size_of', 'validates_length_of', @@ -56,24 +56,24 @@ class Validations 'validates_format_of', 'validates_numericality_of', 'validates_uniqueness_of' - ); + ]; - private static $DEFAULT_VALIDATION_OPTIONS = array( + private static $DEFAULT_VALIDATION_OPTIONS = [ 'on' => 'save', 'allow_null' => false, 'allow_blank' => false, 'message' => null, - ); + ]; - private static $ALL_RANGE_OPTIONS = array( + private static $ALL_RANGE_OPTIONS = [ 'is' => null, 'within' => null, 'in' => null, 'minimum' => null, 'maximum' => null, - ); + ]; - private static $ALL_NUMERICALITY_CHECKS = array( + private static $ALL_NUMERICALITY_CHECKS = [ 'greater_than' => null, 'greater_than_or_equal_to' => null, 'equal_to' => null, @@ -81,7 +81,7 @@ class Validations 'less_than_or_equal_to' => null, 'odd' => null, 'even' => null - ); + ]; /** * Constructs a {@link Validations} object. @@ -109,7 +109,7 @@ public function get_record() */ public function rules() { - $data = array(); + $data = []; foreach ($this->validators as $validate) { $attrs = $this->klass->getStaticPropertyValue($validate); @@ -117,7 +117,7 @@ public function rules() $field = $attr[0]; if (!isset($data[$field]) || !is_array($data[$field])) { - $data[$field] = array(); + $data[$field] = []; } $attr['validator'] = $validate; @@ -155,10 +155,10 @@ public function validate() * * * class Person extends ActiveRecord\Model { - * static $validates_presence_of = array( - * array('first_name'), - * array('last_name') - * ); + * static $validates_presence_of = [ + * ['first_name'], + * ['last_name'] + * ]; * } * * @@ -174,7 +174,7 @@ public function validate() */ public function validates_presence_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save')); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, ['message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save']); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); @@ -187,9 +187,9 @@ public function validates_presence_of($attrs) * * * class Car extends ActiveRecord\Model { - * static $validates_inclusion_of = array( - * array('fuel_type', 'in' => array('hyrdogen', 'petroleum', 'electric')), - * ); + * static $validates_inclusion_of = [ + * ['fuel_type', 'in' => ['hyrdogen', 'petroleum', 'electric']], + * ]; * } * * @@ -248,7 +248,7 @@ public function validates_exclusion_of($attrs) */ public function validates_inclusion_or_exclusion_of($type, $attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save')); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, ['message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save']); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); @@ -282,9 +282,9 @@ public function validates_inclusion_or_exclusion_of($type, $attrs) * * * class Person extends ActiveRecord\Model { - * static $validates_numericality_of = array( - * array('salary', 'greater_than' => 19.99, 'less_than' => 99.99) - * ); + * static $validates_numericality_of = [ + * ['salary', 'greater_than' => 19.99, 'less_than' => 99.99] + * ]; * } * * @@ -307,7 +307,7 @@ public function validates_inclusion_or_exclusion_of($type, $attrs) */ public function validates_numericality_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('only_integer' => false)); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, ['only_integer' => false]); // Notice that for fixnum and float columns empty strings are converted to nil. // Validates whether the value of the specified attribute is numeric by trying to convert it to a float with Kernel.Float @@ -325,7 +325,7 @@ public function validates_numericality_of($attrs) $not_a_number_message = (isset($options['message']) ? $options['message'] : Errors::$DEFAULT_ERROR_MESSAGES['not_a_number']); - if (true === $options['only_integer'] && !is_integer($var)) { + if (true === $options['only_integer'] && !is_int($var)) { if (!preg_match('/\A[+-]?\d+\Z/', (string)($var))) { $this->record->add($attribute, $not_a_number_message); continue; @@ -387,9 +387,9 @@ public function validates_size_of($attrs) * * * class Person extends ActiveRecord\Model { - * static $validates_format_of = array( - * array('email', 'with' => '/^.*?@.*$/') - * ); + * static $validates_format_of = [ + * ['email', 'with' => '/^.*?@.*$/'] + * ]; * } * * @@ -406,7 +406,7 @@ public function validates_size_of($attrs) */ public function validates_format_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null)); + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, ['message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null]); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); @@ -434,9 +434,9 @@ public function validates_format_of($attrs) * * * class Person extends ActiveRecord\Model { - * static $validates_length_of = array( - * array('name', 'within' => array(1,50)) - * ); + * static $validates_length_of = [ + * ['name', 'within' => [1,50]] + * ]; * } * * @@ -444,7 +444,7 @@ public function validates_format_of($attrs) * *