From 10453ec3e707e069c599beb64ff61a7e968eba9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Wed, 9 Sep 2026 20:17:35 +0200 Subject: [PATCH 01/12] Add GitHub Actions matrix for MySQL, PostgreSQL and PHP versions Test on a baseline of PHP 8.4, MySQL 8.4 and PostgreSQL 18, plus one job per other version of each axis (MySQL 5.7 and 8.0, PostgreSQL 14 and 16, PHP 8.2, 8.3 and 8.5). Database data lives on tmpfs so the suite's tens of thousands of small commits do not wait on fsync. A compose.yaml offers the same servers locally, and CONTRIBUTING.md explains how to use them. Fixes uncovered while making the matrix pass: - PgsqlAdapter read pg_attrdef.adsrc, removed in PostgreSQL 12, so schema introspection failed on every supported PostgreSQL. Use pg_get_expr(). - MysqlAdapter emitted datetime values with a time zone offset, which MySQL before 8.0.19 (and MariaDB) rejects under strict sql_mode. Format plain wall-clock time for those servers. - DatabaseTest left an unreachable adapter as the default connection when setUp() skipped, because PHPUnit 11 does not run tearDown() then. That silently skipped 278 unrelated tests whenever PostgreSQL was down. - The connect-with-port test ignored the configured port. - phpunit.xml.dist used PHPUnit 9 attributes. Remove the obsolete Travis configuration. --- .github/workflows/tests.yml | 91 +++++++++++++++++++++++++++++++++++ .travis.yml | 37 -------------- CONTRIBUTING.md | 36 +++++++++++--- compose.yaml | 63 ++++++++++++++++++++++++ lib/Adapters/MysqlAdapter.php | 27 +++++++++++ lib/Adapters/PgsqlAdapter.php | 2 +- phpunit.xml.dist | 9 ++-- test/helpers/AdapterTest.php | 11 ++++- test/helpers/DatabaseTest.php | 9 ++-- 9 files changed, 229 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml create mode 100644 compose.yaml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..b3cdf6cbd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,91 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + phpunit: + name: PHP ${{ matrix.php }} / MySQL ${{ matrix.mysql }} / PostgreSQL ${{ matrix.pgsql }} + 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. + # 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' } + # PostgreSQL axis + - { php: '8.4', mysql: '8.4', pgsql: '14' } + - { php: '8.4', mysql: '8.4', pgsql: '16' } + # 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: + image: mysql:${{ 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 "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@v4 + + - 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_MYSQL: mysql://test:test@127.0.0.1/test + PHPAR_PGSQL: pgsql://test:test@127.0.0.1/test 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/CONTRIBUTING.md b/CONTRIBUTING.md index 2d1cb1478..4e9746ffe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,17 +27,37 @@ 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, memcached and Oracle 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. -* 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, `pgsql14` is 54314, +`pgsql16` is 54316 and `pgsql18` is 54318. 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. Update the matrix when a PHP or database version reaches end of life. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..8cde06ad4 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,63 @@ +# 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"] + + 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/lib/Adapters/MysqlAdapter.php b/lib/Adapters/MysqlAdapter.php index d83a07b2f..7ce5a510a 100644 --- a/lib/Adapters/MysqlAdapter.php +++ b/lib/Adapters/MysqlAdapter.php @@ -19,6 +19,8 @@ class MysqlAdapter extends Connection { static $DEFAULT_PORT = 3306; + private $supports_datetime_offsets; + public function limit($sql, $offset, $limit) { $offset = is_null($offset) ? '' : intval($offset) . ','; @@ -70,6 +72,31 @@ public function create_column(&$column) return $c; } + /** + * MySQL accepts a time zone offset in DATETIME literals only since 8.0.19. + * Older servers, and MariaDB, reject the offset under the default strict + * sql_mode, so they get the plain wall-clock value instead. + */ + public function datetime_to_string($datetime) + { + if ($this->supports_datetime_offsets()) { + return parent::datetime_to_string($datetime); + } + + return $datetime->format('Y-m-d H:i:s'); + } + + private function supports_datetime_offsets() + { + if ($this->supports_datetime_offsets === null) { + $version = (string)$this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION); + $this->supports_datetime_offsets = stripos($version, 'mariadb') === false + && version_compare($version, '8.0.19', '>='); + } + + return $this->supports_datetime_offsets; + } + public function set_encoding($charset) { $params = array($charset); diff --git a/lib/Adapters/PgsqlAdapter.php b/lib/Adapters/PgsqlAdapter.php index 9a02cdf02..ae974e733 100644 --- a/lib/Adapters/PgsqlAdapter.php +++ b/lib/Adapters/PgsqlAdapter.php @@ -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 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 41e4ea6af..57375171c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,11 +1,12 @@ get_default_connection(); $url = parse_url($config->get_connection($name)); $conn = $this->conn; - $port = $conn::$DEFAULT_PORT; + $port = $url['port'] ?? $conn::$DEFAULT_PORT; $connection_string = "{$url['scheme']}://{$url['user']}"; if (isset($url['pass'])) { @@ -429,6 +429,15 @@ public function test_datetime_to_string() { $datetime = '2009-01-01 01:01:01 EST'; $expected_datetime = '2009-01-01 01:01:01-05:00'; + + if ($this->conn->protocol == 'mysql') { + // Time zone offsets in DATETIME literals need MySQL 8.0.19 or later. + $version = $this->conn->connection->getAttribute(PDO::ATTR_SERVER_VERSION); + if (stripos($version, 'mariadb') !== false || version_compare($version, '8.0.19', '<')) { + $expected_datetime = '2009-01-01 01:01:01'; + } + } + $this->assert_equals($expected_datetime, $this->conn->datetime_to_string(date_create($datetime))); } diff --git a/test/helpers/DatabaseTest.php b/test/helpers/DatabaseTest.php index 7e83cec7b..41a6f4429 100644 --- a/test/helpers/DatabaseTest.php +++ b/test/helpers/DatabaseTest.php @@ -41,6 +41,10 @@ public function setUp(): void try { $this->conn = ConnectionManager::get_connection($this->connection_name); } catch (DatabaseException $e) { + // PHPUnit does not run tearDown() when setUp() skips the test, so undo + // the default connection change here. Otherwise an unreachable adapter + // stays as the default and every later test class is skipped as well. + $config->set_default_connection($this->original_default_connection); $this->mark_test_skipped($this->connection_name . ' failed to connect. ' . $e->getMessage()); } @@ -56,11 +60,6 @@ public function setUp(): void public function tearDown(): void { - if ($this->status()->asString() == "skipped") { - // Nothing left to do on skipped test cases - return; - } - Config::instance()->set_date_class($this->original_date_class); if ($this->original_default_connection) { From 5d6d2bed831e9533fdaf30376118a39478a34d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Wed, 9 Sep 2026 20:45:47 +0200 Subject: [PATCH 02/12] Fix cache tests and Memcached exception after the PSR-4 move The first CI run was the first time the memcached tests ran since the models and exceptions moved into namespaces: - ActiveRecordCacheTest::setUp() called parent::set_up(), which the snake_case __call() helper turned back into setUp() on the same class. The infinite recursion hit the stack limit on PHP 8.3+ after minutes and segfaulted PHP 8.2. Call parent::setUp() directly. - CacheModelTest reflected on the bare class names "Author" and "Publisher", which no longer exist; use the namespaced classes. The model cache key now carries the namespace, so expect that too. - lib/cache/Memcached.php threw ActiveRecord\CacheException, which no longer exists, so a failed connection surfaced as a class-not-found Error instead of ActiveRecord\Exceptions\CacheException. --- lib/cache/Memcached.php | 2 ++ test/ActiveRecordCacheTest.php | 14 +++++++++----- test/CacheModelTest.php | 18 +++++++++--------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/cache/Memcached.php b/lib/cache/Memcached.php index 0629fabc8..937660459 100644 --- a/lib/cache/Memcached.php +++ b/lib/cache/Memcached.php @@ -2,6 +2,8 @@ namespace ActiveRecord; +use ActiveRecord\Exceptions\CacheException; + class Memcached { const DEFAULT_PORT = 11211; diff --git a/test/ActiveRecordCacheTest.php b/test/ActiveRecordCacheTest.php index 631652998..7817bc9a9 100644 --- a/test/ActiveRecordCacheTest.php +++ b/test/ActiveRecordCacheTest.php @@ -1,7 +1,11 @@ set_cache('memcached://localhost'); - } catch (ActiveRecord\CacheException $e) { + Config::instance()->set_cache('memcached://localhost'); + } catch (CacheException $e) { $this->markTestSkipped('Unable to connect to memcached server'); } - parent::set_up($connection_name); + parent::setUp(); } public function tearDown(): void @@ -34,7 +38,7 @@ public function test_default_expire() public function test_explicit_default_expire() { - ActiveRecord\Config::instance()->set_cache('memcached://localhost', array('expire' => 1)); + Config::instance()->set_cache('memcached://localhost', array('expire' => 1)); $this->assert_equals(1, Cache::$options['expire']); } @@ -42,7 +46,7 @@ public function test_caches_column_meta_data() { Author::first(); - $table_name = Author::table()->get_fully_qualified_table_name(!($this->conn instanceof ActiveRecord\PgsqlAdapter)); + $table_name = Author::table()->get_fully_qualified_table_name(!($this->conn instanceof PgsqlAdapter)); $value = Cache::$adapter->read("get_meta_data-$table_name"); $this->assert_true(is_array($value)); } diff --git a/test/CacheModelTest.php b/test/CacheModelTest.php index c5ae59c65..07764f025 100644 --- a/test/CacheModelTest.php +++ b/test/CacheModelTest.php @@ -51,16 +51,16 @@ public function test_explicit_expire() public function test_cache_key() { - $method = $this->set_method_public('Author', 'cache_key'); + $method = $this->set_method_public(Author::class, 'cache_key'); $author = Author::first(); - $this->assert_equals("Author-1", $method->invokeArgs($author, array())); + $this->assert_equals(Author::class . "-1", $method->invokeArgs($author, array())); } public function test_model_cache_find_by_pk() { $publisher = Publisher::find(1); - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $cache_key = $method->invokeArgs($publisher, array()); $from_cache = Cache::$adapter->read($cache_key); @@ -74,7 +74,7 @@ public function test_model_cache_new() )); $publisher->save(); - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $cache_key = $method->invokeArgs($publisher, array()); // Model is cached on first find @@ -86,7 +86,7 @@ public function test_model_cache_new() public function test_model_cache_find() { - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $publishers = Publisher::all(); foreach ($publishers as $publisher) { @@ -99,7 +99,7 @@ public function test_model_cache_find() public function test_regular_models_not_cached() { - $method = $this->set_method_public('Author', 'cache_key'); + $method = $this->set_method_public(Author::class, 'cache_key'); $author = Author::first(); $cache_key = $method->invokeArgs($author, array()); $this->assertFalse(Cache::$adapter->read($cache_key)); @@ -107,7 +107,7 @@ public function test_regular_models_not_cached() public function test_model_delete_from_cache() { - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); $cache_key = $method->invokeArgs($publisher, array()); @@ -119,7 +119,7 @@ public function test_model_delete_from_cache() public function test_model_update_cache() { - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); $cache_key = $method->invokeArgs($publisher, array()); @@ -140,7 +140,7 @@ public function test_model_update_cache() public function test_model_reload_expires_cache() { - $method = $this->set_method_public('Publisher', 'cache_key'); + $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); $cache_key = $method->invokeArgs($publisher, array()); From 9d8f14c402167eea0d7c7f004a108cc095e007e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Wed, 9 Sep 2026 20:53:42 +0200 Subject: [PATCH 03/12] Remove Oracle support The OCI adapter was never completed, has no CI coverage, and its tests have been skipped everywhere for years. Drop the adapter, its tests and SQL fixtures, the Oracle-only branches in Model and the test helpers, and the mentions in the documentation. --- CHANGELOG | 1 - CONTRIBUTING.md | 4 +- README.md | 1 - lib/Adapters/OciAdapter.php | 157 -------------------------------- lib/Model.php | 24 ++--- test/ActiveRecordFindTest.php | 19 +--- test/ActiveRecordTest.php | 9 -- test/ActiveRecordWriteTest.php | 5 - test/OciAdapterTest.php | 59 ------------ test/helpers/AdapterTest.php | 7 -- test/helpers/DatabaseLoader.php | 24 ----- test/helpers/config.php | 1 - test/sql/oci-after-fixtures.sql | 38 -------- test/sql/oci.sql | 104 --------------------- 14 files changed, 13 insertions(+), 440 deletions(-) delete mode 100644 lib/Adapters/OciAdapter.php delete mode 100644 test/OciAdapterTest.php delete mode 100644 test/sql/oci-after-fixtures.sql delete mode 100644 test/sql/oci.sql 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 4e9746ffe..4d0148e76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,8 +29,8 @@ vendor/bin/phpunit test/InflectorTest.php #### Databases #### -Most tests need a MySQL server, and the PostgreSQL, memcached and Oracle tests are skipped when their -server or PHP extension is missing. To see why a test was skipped: +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 --display-skipped diff --git a/README.md b/README.md index 9f7fe5a4a..a539407f6 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 ## 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/Model.php b/lib/Model.php index c5c01d7e7..02844c70f 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -834,20 +834,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); } @@ -1249,11 +1242,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); } diff --git a/test/ActiveRecordFindTest.php b/test/ActiveRecordFindTest.php index 6f622173d..43dea70db 100644 --- a/test/ActiveRecordFindTest.php +++ b/test/ActiveRecordFindTest.php @@ -11,7 +11,6 @@ use ActiveRecord\Exceptions\DatabaseException; use ActiveRecord\Exceptions\ActiveRecordException; use ActiveRecord\Exceptions\UndefinedPropertyException; -use ActiveRecord\OciAdapter; class ActiveRecordFindTest extends DatabaseTest { @@ -377,19 +376,11 @@ public function test_from() public function test_having() { - if ($this->conn instanceof OciAdapter) { - $author = Author::first(array( - 'select' => 'to_char(created_at,\'YYYY-MM-DD\') as created_at', - 'group' => 'to_char(created_at,\'YYYY-MM-DD\')', - 'having' => "to_char(created_at,'YYYY-MM-DD') > '2009-01-01'")); - $this->assert_sql_has("GROUP BY to_char(created_at,'YYYY-MM-DD') HAVING to_char(created_at,'YYYY-MM-DD') > '2009-01-01'", Author::table()->last_sql); - } else { - $author = Author::first(array( - 'select' => 'date(created_at) as created_at', - 'group' => 'date(created_at)', - 'having' => "date(created_at) > '2009-01-01'")); - $this->assert_sql_has("GROUP BY date(created_at) HAVING date(created_at) > '2009-01-01'", Author::table()->last_sql); - } + $author = Author::first(array( + 'select' => 'date(created_at) as created_at', + 'group' => 'date(created_at)', + 'having' => "date(created_at) > '2009-01-01'")); + $this->assert_sql_has("GROUP BY date(created_at) HAVING date(created_at) > '2009-01-01'", Author::table()->last_sql); } public function test_from_with_invalid_table() diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 6c5554d51..7c3bbaf9e 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -9,7 +9,6 @@ use TestModels\BookAttrAccessible; use ActiveRecord\Table; use ActiveRecord\Config; -use ActiveRecord\Adapters\OciAdapter; use ActiveRecord\Exceptions\ReadonlyException; use ActiveRecord\Exceptions\ActiveRecordException; use ActiveRecord\Exceptions\UndefinedPropertyException; @@ -132,20 +131,12 @@ public function test_get_values_for() public function test_hyphenated_column_names_to_underscore() { - if ($this->conn instanceof OciAdapter) { - return; - } - $keys = array_keys(RmBldg::first()->attributes()); $this->assert_true(in_array('rm_name', $keys)); } public function test_column_names_with_spaces() { - if ($this->conn instanceof OciAdapter) { - return; - } - $keys = array_keys(RmBldg::first()->attributes()); $this->assert_true(in_array('space_out', $keys)); } diff --git a/test/ActiveRecordWriteTest.php b/test/ActiveRecordWriteTest.php index aa67e898f..de01f2002 100644 --- a/test/ActiveRecordWriteTest.php +++ b/test/ActiveRecordWriteTest.php @@ -191,11 +191,6 @@ public function test_save_null_value() public function test_save_blank_value() { - // oracle doesn't do blanks. probably an option to enable? - if ($this->conn instanceof ActiveRecord\OciAdapter) { - return; - } - $book = Book::find(1); $book->name = ''; $book->save(); diff --git a/test/OciAdapterTest.php b/test/OciAdapterTest.php deleted file mode 100644 index 14af3e4c7..000000000 --- a/test/OciAdapterTest.php +++ /dev/null @@ -1,59 +0,0 @@ -connection_name = 'oci'; - parent::setUp(); - } - - public function test_get_sequence_name() - { - $this->assert_equals('authors_seq', $this->conn->get_sequence_name('authors', 'author_id')); - } - - public function test_columns_text() - { - $author_columns = $this->conn->columns('authors'); - $this->assert_equals('varchar2', $author_columns['some_text']->raw_type); - $this->assert_equals(100, $author_columns['some_text']->length); - } - - public function test_datetime_to_string() - { - $this->assert_equals('01-Jan-2009 01:01:01 AM', $this->conn->datetime_to_string(date_create('2009-01-01 01:01:01 EST'))); - } - - public function test_date_to_string() - { - $this->assert_equals('01-Jan-2009', $this->conn->date_to_string(date_create('2009-01-01 01:01:01 EST'))); - } - - public function test_insert_id() - { - } - public function test_insert_id_with_params() - { - } - public function test_insert_id_should_return_explicitly_inserted_id() - { - } - public function test_columns_time() - { - } - public function test_columns_sequence() - { - } - - public function test_set_charset() - { - $connection_string = Config::instance()->get_connection($this->connection_name); - $conn = Connection::instance($connection_string . '?charset=utf8'); - $this->assert_equals(';charset=utf8', $conn->dsn_params); - } -} diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index 354860799..ce20ca3de 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -8,7 +8,6 @@ use ActiveRecord\Config; use ActiveRecord\Column; use ActiveRecord\Connection; -use ActiveRecord\Adapters\OciAdapter; use ActiveRecord\Adapters\SqliteAdapter; use ActiveRecord\Exceptions\DatabaseException; use TestHelpers\DatabaseTest; @@ -287,12 +286,6 @@ public function test_columnsx() $columns = $this->conn->columns('authors'); $names = array('author_id','parent_author_id','name','updated_at','created_at','some_Date','some_time','some_text','encrypted_password','mixedCaseField'); - if ($this->conn instanceof OciAdapter) { - $names = array_filter(array_map('strtolower', $names), function ($s) { - return $s !== 'some_time'; - }); - } - foreach ($names as $field) { $this->assert_true(array_key_exists($field, $columns)); } diff --git a/test/helpers/DatabaseLoader.php b/test/helpers/DatabaseLoader.php index ab476e211..ec29828fa 100644 --- a/test/helpers/DatabaseLoader.php +++ b/test/helpers/DatabaseLoader.php @@ -27,10 +27,6 @@ public function __construct($db) public function reset_table_data() { foreach ($this->get_fixture_tables() as $table) { - if ($this->db->protocol == 'oci' && $table == 'rm-bldg') { - continue; - } - $this->db->query('DELETE FROM ' . $this->quote_name($table)); $this->load_fixture_data($table); } @@ -48,25 +44,9 @@ public function drop_tables() $tables = $this->db->tables(); foreach ($this->get_fixture_tables() as $table) { - if ($this->db->protocol == 'oci') { - $table = strtoupper($table); - - if ($table == 'RM-BLDG') { - continue; - } - } - if (in_array($table, $tables)) { $this->db->query('DROP TABLE ' . $this->quote_name($table)); } - - if ($this->db->protocol == 'oci') { - try { - $this->db->query("DROP SEQUENCE {$table}_seq"); - } catch (ActiveRecord\DatabaseException $e) { - // ignore - } - } } } @@ -126,10 +106,6 @@ public function load_fixture_data($table) public function quote_name($name) { - if ($this->db->protocol == 'oci') { - $name = strtoupper($name); - } - return $this->db->quote_name($name); } } diff --git a/test/helpers/config.php b/test/helpers/config.php index 6e071b9bc..68c46f6d8 100644 --- a/test/helpers/config.php +++ b/test/helpers/config.php @@ -37,7 +37,6 @@ $cfg->set_connections(array( 'mysql' => getenv('PHPAR_MYSQL') ?: 'mysql://test:test@127.0.0.1/test', 'pgsql' => getenv('PHPAR_PGSQL') ?: 'pgsql://test:test@127.0.0.1/test', - 'oci' => getenv('PHPAR_OCI') ?: 'oci://test:test@127.0.0.1/dev', 'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db')); $cfg->set_default_connection('mysql'); diff --git a/test/sql/oci-after-fixtures.sql b/test/sql/oci-after-fixtures.sql deleted file mode 100644 index 6da0a96f2..000000000 --- a/test/sql/oci-after-fixtures.sql +++ /dev/null @@ -1,38 +0,0 @@ -DROP SEQUENCE authors_seq; -CREATE SEQUENCE authors_seq START WITH 100; - -DROP SEQUENCE books_seq; -CREATE SEQUENCE books_seq START WITH 100; - -DROP SEQUENCE publishers_seq; -CREATE SEQUENCE publishers_seq START WITH 100; - -DROP SEQUENCE venues_seq; -CREATE SEQUENCE venues_seq START WITH 100; - -DROP SEQUENCE events_seq; -CREATE SEQUENCE events_seq START WITH 100; - -DROP SEQUENCE hosts_seq; -CREATE SEQUENCE hosts_seq START WITH 100; - -DROP SEQUENCE employees_seq; -CREATE SEQUENCE employees_seq START WITH 100; - -DROP SEQUENCE positions_seq; -CREATE SEQUENCE positions_seq START WITH 100; - -DROP SEQUENCE awesome_people_seq; -CREATE SEQUENCE awesome_people_seq START WITH 100; - -DROP SEQUENCE amenities_seq; -CREATE SEQUENCE amenities_seq START WITH 100; - -DROP SEQUENCE property_seq; -CREATE SEQUENCE property_seq START WITH 100; - -DROP SEQUENCE property_amenities_seq; -CREATE SEQUENCE property_amenities_seq START WITH 100; - -DROP SEQUENCE valuestore_seq; -CREATE SEQUENCE valuestore_seq START WITH 100; diff --git a/test/sql/oci.sql b/test/sql/oci.sql deleted file mode 100644 index 6f083a4d4..000000000 --- a/test/sql/oci.sql +++ /dev/null @@ -1,104 +0,0 @@ -CREATE SEQUENCE authors_seq; -CREATE TABLE authors( - author_id INT NOT NULL PRIMARY KEY, - parent_author_id INT, - publisher_id INT, - name VARCHAR(25) DEFAULT 'default_name' NOT NULL, - updated_at timestamp, - created_at timestamp, - some_date date, - --some_time time, - some_text varchar2(100), - encrypted_password varchar(50), - "mixedCaseField" varchar(50) -); - -CREATE SEQUENCE books_seq; -CREATE TABLE books( - book_id INT NOT NULL PRIMARY KEY, - Author_Id INT, - secondary_author_id INT, - name VARCHAR(50), - numeric_test VARCHAR(10) DEFAULT '0', - special NUMERIC(10,2) DEFAULT 0); - -CREATE SEQUENCE publishers_seq; -CREATE TABLE publishers( - publisher_id INT NOT NULL PRIMARY KEY, - name VARCHAR(25) DEFAULT 'default_name' NOT NULL, -); - -CREATE SEQUENCE venues_seq; -CREATE TABLE venues ( - Id INT NOT NULL PRIMARY KEY, - name varchar(50), - city varchar(60), - state char(2), - address varchar(50), - phone varchar(10) default NULL, - UNIQUE(name,address) -); - -CREATE SEQUENCE events_seq; -CREATE TABLE events ( - id INT NOT NULL PRIMARY KEY, - venue_id int NULL, - host_id int NOT NULL, - title varchar(60) NOT NULL, - description varchar(10), - type varchar(15) default NULL -); - -CREATE SEQUENCE hosts_seq; -CREATE TABLE hosts( - id INT NOT NULL PRIMARY KEY, - name VARCHAR(25) -); - -CREATE SEQUENCE employees_seq; -CREATE TABLE employees ( - id INT NOT NULL PRIMARY KEY, - first_name VARCHAR( 255 ) NOT NULL , - last_name VARCHAR( 255 ) NOT NULL , - nick_name VARCHAR( 255 ) NOT NULL -); - -CREATE SEQUENCE positions_seq; -CREATE TABLE positions ( - id INT NOT NULL PRIMARY KEY, - employee_id int NOT NULL, - title VARCHAR(255) NOT NULL, - active SMALLINT NOT NULL -); - -CREATE SEQUENCE awesome_people_seq; -CREATE TABLE awesome_people( - id int not null primary key, - author_id int, - is_awesome int default 1 -); - -CREATE SEQUENCE amenities_seq; -CREATE TABLE amenities( - amenity_id int primary key, - type varchar(40) NOT NULL -); - -CREATE SEQUENCE property_seq; -CREATE TABLE property( - property_id int primary key -); - -CREATE SEQUENCE property_amenities_seq; -CREATE TABLE property_amenities( - id int primary key, - amenity_id int not null, - property_id int not null -); - -CREATE SEQUENCE valuestore_seq; -CREATE TABLE valuestore( - id int primary key, - `key` varchar(20) NOT NULL DEFAULT '', - `value` varchar(255) NOT NULL DEFAULT '' -); \ No newline at end of file From ddb84d9203ddfc91e08feece889b2cdb71c29eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Wed, 9 Sep 2026 23:57:33 +0200 Subject: [PATCH 04/12] Write datetimes as plain wall-clock time on every adapter The base datetime format appended PHP's time zone offset. MySQL before 8.0.19, MariaDB and SQLite do not understand it, and MySQL 8.0.19+ makes it worse by converting the value to the session time zone, so a value only round-tripped when that happened to match PHP's zone; with PHP in Europe/Madrid and a UTC server, 18:00 came back as 16:00. Use 'Y-m-d H:i:s' everywhere, drop the per-version MySQL override and the now identical SQLite override, and add a round-trip test that runs with PHP in a non-UTC zone on every adapter. --- lib/Adapters/MysqlAdapter.php | 27 --------------------------- lib/Adapters/SqliteAdapter.php | 2 -- lib/Connection.php | 10 ++++++++-- test/SqliteAdapterTest.php | 6 ------ test/helpers/AdapterTest.php | 29 +++++++++++++++++++---------- 5 files changed, 27 insertions(+), 47 deletions(-) diff --git a/lib/Adapters/MysqlAdapter.php b/lib/Adapters/MysqlAdapter.php index 7ce5a510a..d83a07b2f 100644 --- a/lib/Adapters/MysqlAdapter.php +++ b/lib/Adapters/MysqlAdapter.php @@ -19,8 +19,6 @@ class MysqlAdapter extends Connection { static $DEFAULT_PORT = 3306; - private $supports_datetime_offsets; - public function limit($sql, $offset, $limit) { $offset = is_null($offset) ? '' : intval($offset) . ','; @@ -72,31 +70,6 @@ public function create_column(&$column) return $c; } - /** - * MySQL accepts a time zone offset in DATETIME literals only since 8.0.19. - * Older servers, and MariaDB, reject the offset under the default strict - * sql_mode, so they get the plain wall-clock value instead. - */ - public function datetime_to_string($datetime) - { - if ($this->supports_datetime_offsets()) { - return parent::datetime_to_string($datetime); - } - - return $datetime->format('Y-m-d H:i:s'); - } - - private function supports_datetime_offsets() - { - if ($this->supports_datetime_offsets === null) { - $version = (string)$this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION); - $this->supports_datetime_offsets = stripos($version, 'mariadb') === false - && version_compare($version, '8.0.19', '>='); - } - - return $this->supports_datetime_offsets; - } - public function set_encoding($charset) { $params = array($charset); diff --git a/lib/Adapters/SqliteAdapter.php b/lib/Adapters/SqliteAdapter.php index f4340c10c..99601f322 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)) { diff --git a/lib/Connection.php b/lib/Connection.php index ef0ae779a..a3ccb4f27 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -68,10 +68,16 @@ 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. diff --git a/test/SqliteAdapterTest.php b/test/SqliteAdapterTest.php index 3064125bf..8228da951 100644 --- a/test/SqliteAdapterTest.php +++ b/test/SqliteAdapterTest.php @@ -64,12 +64,6 @@ public function test_gh183_sqliteadapter_autoincrement() $this->assert_true($columns['id']->auto_increment); } - public function test_datetime_to_string() - { - $datetime = '2009-01-01 01:01:01'; - $this->assert_equals($datetime, $this->conn->datetime_to_string(date_create($datetime))); - } - public function test_date_to_string() { $datetime = '2009-01-01'; diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index ce20ca3de..64845f426 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -11,6 +11,7 @@ use ActiveRecord\Adapters\SqliteAdapter; use ActiveRecord\Exceptions\DatabaseException; use TestHelpers\DatabaseTest; +use TestModels\Author; class AdapterTest extends DatabaseTest { @@ -421,19 +422,27 @@ public function test_quote_name_does_not_over_quote() public function test_datetime_to_string() { $datetime = '2009-01-01 01:01:01 EST'; - $expected_datetime = '2009-01-01 01:01:01-05:00'; - - if ($this->conn->protocol == 'mysql') { - // Time zone offsets in DATETIME literals need MySQL 8.0.19 or later. - $version = $this->conn->connection->getAttribute(PDO::ATTR_SERVER_VERSION); - if (stripos($version, 'mariadb') !== false || version_compare($version, '8.0.19', '<')) { - $expected_datetime = '2009-01-01 01:01:01'; - } - } - + $expected_datetime = '2009-01-01 01:01:01'; $this->assert_equals($expected_datetime, $this->conn->datetime_to_string(date_create($datetime))); } + public function test_datetime_round_trip_keeps_wall_clock_time_in_any_timezone() + { + $original_timezone = date_default_timezone_get(); + date_default_timezone_set('Europe/Madrid'); + + try { + $author = Author::find(1); + $author->created_at = new \DateTime('2026-09-09 18:00:00'); + $author->save(); + + $reloaded = Author::find(1); + $this->assert_equals('2026-09-09 18:00:00 +02:00', $reloaded->created_at->format('Y-m-d H:i:s P')); + } finally { + date_default_timezone_set($original_timezone); + } + } + public function test_date_to_string() { $datetime = '2009-01-01'; From 9830a45e214556a6001520523f0824b4ea451ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 00:02:09 +0200 Subject: [PATCH 05/12] Fix model column metadata on PostgreSQL Table checked `$this->conn instanceof PgsqlAdapter` without importing ActiveRecord\Adapters\PgsqlAdapter, so since the PSR-4 move the check was always false, the table name was quoted, and the pg_catalog lookup matched nothing. Models on PostgreSQL therefore had no columns: no type casting, no defaults, no primary key detection. The adapter tests never noticed because they query the connection directly; the new datetime round-trip test is the first model-level test that runs on PostgreSQL. --- lib/Table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Table.php b/lib/Table.php index f05748c2c..68042eb49 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -7,6 +7,7 @@ namespace ActiveRecord; use ActiveRecord\Exceptions\RelationshipException; +use ActiveRecord\Adapters\PgsqlAdapter; /** * Manages reading and writing to a database table. From be500a533b0f17c521c18bb7467b79326e577c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 00:11:09 +0200 Subject: [PATCH 06/12] Make the library fully PSR-4 autoloaded Remove every require/require_once from lib: SQLBuilder and Model pulled in Expressions and Validations by hand, Cache loaded its backend from a file path, and Connection and Table still carried commented-out ones. Move the memcached backend to lib/Cache/Memcached.php as ActiveRecord\Cache\Memcached, where PSR-4 finds it; Cache::initialize() now resolves the backend class by scheme and throws CacheException for an unknown one instead of failing on a missing file. Add the imports that Reflections and Serialization were missing for their exception classes since the move to ActiveRecord\Exceptions, and fix three tests importing DatabaseException from its old location. The has-many-through test models in the foo\bar\biz namespace move from an included helper file to test/models/biz with an autoload-dev entry. --- composer.json | 3 ++- lib/AbstractRelationship.php | 2 -- lib/Cache.php | 12 ++++++++---- lib/{cache => Cache}/Memcached.php | 2 +- lib/Connection.php | 6 ------ lib/Model.php | 4 ---- lib/Reflections.php | 1 + lib/SQLBuilder.php | 2 -- lib/Serialization.php | 2 ++ lib/Table.php | 3 +-- test/ColumnTest.php | 2 +- test/DateTimeTest.php | 2 +- test/ExpressionsTest.php | 2 +- test/HasManyThroughTest.php | 2 -- test/helpers/foo.php | 29 ----------------------------- test/models/biz/Newsletter.php | 13 +++++++++++++ test/models/biz/User.php | 13 +++++++++++++ test/models/biz/UserNewsletter.php | 13 +++++++++++++ 18 files changed, 57 insertions(+), 56 deletions(-) rename lib/{cache => Cache}/Memcached.php (97%) delete mode 100644 test/helpers/foo.php create mode 100644 test/models/biz/Newsletter.php create mode 100644 test/models/biz/User.php create mode 100644 test/models/biz/UserNewsletter.php diff --git a/composer.json b/composer.json index 0a8cfd8c9..7134e0f5e 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "autoload-dev": { "psr-4": { "TestHelpers\\": "test/helpers", - "TestModels\\": "test/models" + "TestModels\\": "test/models", + "foo\\bar\\biz\\": "test/models/biz" } } } diff --git a/lib/AbstractRelationship.php b/lib/AbstractRelationship.php index 99a0f15f2..708a0432f 100644 --- a/lib/AbstractRelationship.php +++ b/lib/AbstractRelationship.php @@ -379,5 +379,3 @@ public function construct_inner_join_sql(Table $from_table, $using_through = fal */ abstract public function load(Model $model); } - - diff --git a/lib/Cache.php b/lib/Cache.php index 85b3742d1..fe648f1dc 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() { @@ -27,7 +28,7 @@ class Cache * * Ex: * $cfg_ar = ActiveRecord\Config::instance(); - * $cfg_ar->set_cache('memcache://localhost:11211',array( + * $cfg_ar->set_cache('memcached://localhost:11211',array( * 'namespace' => 'my_cool_app', * 'expire' => 120 * )); @@ -44,9 +45,12 @@ public static function initialize($url, $options = array()) { if ($url) { $url = parse_url($url); - $file = ucwords(Inflector::instance()->camelize($url['scheme'])); - $class = "ActiveRecord\\$file"; - require_once __DIR__ . "/cache/$file.php"; + $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; diff --git a/lib/cache/Memcached.php b/lib/Cache/Memcached.php similarity index 97% rename from lib/cache/Memcached.php rename to lib/Cache/Memcached.php index 937660459..04600689b 100644 --- a/lib/cache/Memcached.php +++ b/lib/Cache/Memcached.php @@ -1,6 +1,6 @@ rules(); } @@ -1090,8 +1088,6 @@ 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'); diff --git a/lib/Reflections.php b/lib/Reflections.php index 8d5c976a0..44300eb34 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. diff --git a/lib/SQLBuilder.php b/lib/SQLBuilder.php index 24f680670..24cfd0325 100644 --- a/lib/SQLBuilder.php +++ b/lib/SQLBuilder.php @@ -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,7 +349,6 @@ private function build_delete() private function build_insert() { - require_once 'Expressions.php'; $keys = join(',', $this->quoted_key_names()); if ($this->sequence) { diff --git a/lib/Serialization.php b/lib/Serialization.php index c8eeb92b7..892e3662a 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -6,6 +6,8 @@ namespace ActiveRecord; +use ActiveRecord\Exceptions\UndefinedPropertyException; + /** * Base class for Model serializers. * diff --git a/lib/Table.php b/lib/Table.php index 68042eb49..d4b42c0a2 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -8,7 +8,7 @@ use ActiveRecord\Exceptions\RelationshipException; use ActiveRecord\Adapters\PgsqlAdapter; - + /** * Manages reading and writing to a database table. * @@ -519,7 +519,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) { diff --git a/test/ColumnTest.php b/test/ColumnTest.php index 57a713cd5..d9cdd5c4d 100644 --- a/test/ColumnTest.php +++ b/test/ColumnTest.php @@ -2,7 +2,7 @@ use ActiveRecord\Column; use ActiveRecord\DateTime; -use ActiveRecord\DatabaseException; +use ActiveRecord\Exceptions\DatabaseException; use TestHelpers\SnakeCase_PHPUnit_Framework_TestCase; class ColumnTest extends SnakeCase_PHPUnit_Framework_TestCase diff --git a/test/DateTimeTest.php b/test/DateTimeTest.php index 99fc86213..31afa8ec8 100644 --- a/test/DateTimeTest.php +++ b/test/DateTimeTest.php @@ -1,7 +1,7 @@ 'user_newsletters') - ); -} - -class Newsletter extends \ActiveRecord\Model -{ - static $has_many = array( - array('user_newsletters'), - array('users', 'through' => 'user_newsletters'), - ); -} - -class UserNewsletter extends \ActiveRecord\Model -{ - static $belong_to = array( - array('user'), - array('newsletter'), - ); -} - -# vim: ts=4 noet nobinary diff --git a/test/models/biz/Newsletter.php b/test/models/biz/Newsletter.php new file mode 100644 index 000000000..7a0581536 --- /dev/null +++ b/test/models/biz/Newsletter.php @@ -0,0 +1,13 @@ + 'user_newsletters'), + ); +} diff --git a/test/models/biz/User.php b/test/models/biz/User.php new file mode 100644 index 000000000..b723f9f53 --- /dev/null +++ b/test/models/biz/User.php @@ -0,0 +1,13 @@ + 'user_newsletters') + ); +} diff --git a/test/models/biz/UserNewsletter.php b/test/models/biz/UserNewsletter.php new file mode 100644 index 000000000..0179c3b98 --- /dev/null +++ b/test/models/biz/UserNewsletter.php @@ -0,0 +1,13 @@ + Date: Thu, 10 Sep 2026 00:28:07 +0200 Subject: [PATCH 07/12] Move the has-many-through test models under TestModels\Biz The GH-101 test needs models whose namespace differs from the configured model namespace, so that relationships must resolve related classes through the owning model's namespace rather than the Config fallback. A sub-namespace of TestModels satisfies that and is already covered by the existing autoload-dev rule, so the extra foo\bar\biz entry goes. --- composer.json | 3 +-- test/HasManyThroughTest.php | 8 ++++---- test/models/{biz => Biz}/Newsletter.php | 2 +- test/models/{biz => Biz}/User.php | 2 +- test/models/{biz => Biz}/UserNewsletter.php | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) rename test/models/{biz => Biz}/Newsletter.php (88%) rename test/models/{biz => Biz}/User.php (88%) rename test/models/{biz => Biz}/UserNewsletter.php (86%) diff --git a/composer.json b/composer.json index 7134e0f5e..0a8cfd8c9 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,7 @@ "autoload-dev": { "psr-4": { "TestHelpers\\": "test/helpers", - "TestModels\\": "test/models", - "foo\\bar\\biz\\": "test/models/biz" + "TestModels\\": "test/models" } } } diff --git a/test/HasManyThroughTest.php b/test/HasManyThroughTest.php index b34d34956..771080b8c 100644 --- a/test/HasManyThroughTest.php +++ b/test/HasManyThroughTest.php @@ -1,7 +1,7 @@ assert_equals($newsletter->id, $user->newsletters[0]->id); $this->assert_equals( - 'foo\bar\biz\Newsletter', + Newsletter::class, get_class($user->newsletters[0]) ); $this->assert_equals($user->id, $newsletter->users[0]->id); $this->assert_equals( - 'foo\bar\biz\User', + User::class, get_class($newsletter->users[0]) ); } diff --git a/test/models/biz/Newsletter.php b/test/models/Biz/Newsletter.php similarity index 88% rename from test/models/biz/Newsletter.php rename to test/models/Biz/Newsletter.php index 7a0581536..c1945068c 100644 --- a/test/models/biz/Newsletter.php +++ b/test/models/Biz/Newsletter.php @@ -1,6 +1,6 @@ Date: Thu, 10 Sep 2026 01:19:54 +0200 Subject: [PATCH 08/12] Make the model layer work on PostgreSQL and add PHPAR_ADAPTER The test bootstrap now reads PHPAR_ADAPTER (mysql, pgsql or sqlite) to pick the default connection, replacing the --adapter switch that PHPUnit 11 rejects, so the model-level tests can run against every adapter. PHPAR_SLOW_TESTS=true replaces --slow-tests. Running the suite with PostgreSQL as the default exposed these library bugs, which MySQL hid: - Expressions rendered a null hash condition as "IS ?" with a bound NULL. MySQL's emulated prepares interpolate that into IS NULL; PostgreSQL rejects a parameter after IS. Render the literal instead. - Writes and hash conditions used the inflected attribute name as the column name (some_date for some_Date). MySQL matches column names case-insensitively; PostgreSQL does not once the name is quoted. Table::column_name_for() resolves the real column name, and eager loading uses it for the related table's keys. - find() with a non-numeric value for an integer primary key raised a type error on PostgreSQL, where MySQL coerced it to 0 and found nothing. Throw RecordNotFound up front on every adapter. Test suite adjustments for engine differences: - PostgreSQL and SQLite schemas made events.host_id NOT NULL while MySQL allowed null; the belongs-to-null test needs the null. - Two tests used MySQL-only SQL (double-quoted string literal, backtick quoting); use standard quotes and quote_name(). - update_all row counts: MySQL reports rows changed, PostgreSQL and SQLite rows matched, so the expectation depends on the adapter. - The SQL builder test expects IS NULL and no bound null; eager-loading assertions read the real column name; the connection-manager test uses the configured default instead of "mysql"; the date-format test reloads by id because first() without an order is arbitrary after an update on PostgreSQL. --- lib/AbstractRelationship.php | 3 +++ lib/Expressions.php | 9 +++++-- lib/Model.php | 11 ++++++++ lib/Table.php | 48 +++++++++++++++++++++++++++++----- test/ActiveRecordWriteTest.php | 15 ++++++++--- test/ConnectionManagerTest.php | 5 ++-- test/DateFormatTest.php | 2 +- test/RelationshipTest.php | 12 ++++----- test/SQLBuilderTest.php | 5 ++-- test/ValidationsTest.php | 2 +- test/helpers/config.php | 13 +++------ test/sql/pgsql.sql | 2 +- test/sql/sqlite.sql | 2 +- 13 files changed, 93 insertions(+), 36 deletions(-) diff --git a/lib/AbstractRelationship.php b/lib/AbstractRelationship.php index 708a0432f..ca8305a86 100644 --- a/lib/AbstractRelationship.php +++ b/lib/AbstractRelationship.php @@ -127,6 +127,9 @@ public function is_poly() */ protected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes = array(), $query_keys = array(), $model_values_keys = 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 = array(); $options = $this->options; $inflector = Inflector::instance(); diff --git a/lib/Expressions.php b/lib/Expressions.php index bf56784cb..ad8b70ccf 100644 --- a/lib/Expressions.php +++ b/lib/Expressions.php @@ -124,6 +124,8 @@ public function to_s($substitute = false, &$options = null) private function build_sql_from_hash(&$hash, $glue) { $sql = $g = ""; + $values = array(); + 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 array($sql, $values); } private function substitute(&$values, $substitute, $pos, $parameter_index) diff --git a/lib/Model.php b/lib/Model.php index 30692942d..f2776a369 100644 --- a/lib/Model.php +++ b/lib/Model.php @@ -1658,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 { diff --git a/lib/Table.php b/lib/Table.php index d4b42c0a2..5c7da7fb7 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -195,7 +195,7 @@ public function options_to_sql($options) $options['conditions'] = $this->map_names($options['conditions'], $options['mapped_names']); } - $sql->where($options['conditions']); + $sql->where($this->map_column_names($options['conditions'])); } } @@ -316,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; @@ -437,6 +455,17 @@ private function map_names(&$hash, &$map) return $ret; } + private function map_column_names($hash) + { + $ret = array(); + + foreach ($hash as $name => $value) { + $ret[$this->column_name_for($name)] = $value; + } + + return $ret; + } + private function &process_data($hash) { if (!$hash) { @@ -444,18 +473,23 @@ private function &process_data($hash) } $date_class = Config::instance()->get_date_class(); - foreach ($hash as $name => &$value) { + $ret = array(); + + 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() diff --git a/test/ActiveRecordWriteTest.php b/test/ActiveRecordWriteTest.php index de01f2002..95cfe2128 100644 --- a/test/ActiveRecordWriteTest.php +++ b/test/ActiveRecordWriteTest.php @@ -385,14 +385,23 @@ public function test_delete_all_with_limit_and_order() public function test_update_all_with_set_as_string() { $num_affected = Author::update_all(array('set' => 'parent_author_id = 2')); - $this->assert_equals(2, $num_affected); + $this->assert_equals($this->expected_update_all_count(), $num_affected); $this->assert_equals(4, Author::count_by_parent_author_id(2)); } public function test_update_all_with_set_as_hash() { $num_affected = Author::update_all(array('set' => array('parent_author_id' => 2))); - $this->assert_equals(2, $num_affected); + $this->assert_equals($this->expected_update_all_count(), $num_affected); + } + + /** + * Four authors match the update above, but two already have parent_author_id = 2. + * MySQL reports rows changed (2) by default; PostgreSQL and SQLite report rows matched (4). + */ + private function expected_update_all_count() + { + return $this->conn->protocol == 'mysql' ? 2 : 4; } /** @@ -406,7 +415,7 @@ public function test_update_all_with_set_as_hash() public function test_update_all_with_conditions_as_string() { - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => 'name = "Tito"')); + $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => "name = 'Tito'")); $this->assert_equals(1, $num_affected); } diff --git a/test/ConnectionManagerTest.php b/test/ConnectionManagerTest.php index af85d0e97..80c3f1b4e 100644 --- a/test/ConnectionManagerTest.php +++ b/test/ConnectionManagerTest.php @@ -32,9 +32,10 @@ public function test_get_connection_with_default() public function test_gh_91_get_connection_with_null_connection_is_always_default() { - $conn_one = ConnectionManager::get_connection('mysql'); + $default = Config::instance()->get_default_connection(); + $conn_one = ConnectionManager::get_connection($default); $conn_two = ConnectionManager::get_connection(); - $conn_three = ConnectionManager::get_connection('mysql'); + $conn_three = ConnectionManager::get_connection($default); $conn_four = ConnectionManager::get_connection(); $this->assert_same($conn_one, $conn_three); diff --git a/test/DateFormatTest.php b/test/DateFormatTest.php index 93047c7b0..2e0253313 100644 --- a/test/DateFormatTest.php +++ b/test/DateFormatTest.php @@ -12,7 +12,7 @@ public function test_datefield_gets_converted_to_ar_datetime() $author->some_date = new \DateTime(); $author->save(); - $author = Author::first(); + $author = Author::find($author->id); $this->assert_is_a("ActiveRecord\\DateTime", $author->some_date); } } diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index 4f483e2f9..ef9f3de20 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -596,7 +596,7 @@ public function test_eager_loading_has_many_with_no_related_rows() $this->assert_true(empty($v->events)); } - $this->assert_sql_has("WHERE id IN(?,?)", Table::load('Venue')->last_sql); + $this->assert_sql_has("WHERE " . Venue::table()->column_name_for('id') . " IN(?,?)", Table::load('Venue')->last_sql); $this->assert_sql_has("WHERE venue_id IN(?,?)", Table::load('Event')->last_sql); } @@ -621,7 +621,7 @@ public function test_eager_loading_has_many_array_of_includes() } $this->assert_sql_has("WHERE author_id IN(?,?)", Table::load('Author')->last_sql); - $this->assert_sql_has("WHERE author_id IN(?,?)", Table::load('Book')->last_sql); + $this->assert_sql_has("WHERE " . Book::table()->column_name_for('author_id') . " IN(?,?)", Table::load('Book')->last_sql); $this->assert_sql_has("WHERE author_id IN(?,?)", Table::load('AwesomePerson')->last_sql); } @@ -640,7 +640,7 @@ public function test_eager_loading_has_many_nested() } } - $this->assert_sql_has("WHERE id IN(?,?)", Table::load('Venue')->last_sql); + $this->assert_sql_has("WHERE " . Venue::table()->column_name_for('id') . " IN(?,?)", Table::load('Venue')->last_sql); $this->assert_sql_has("WHERE venue_id IN(?,?)", Table::load('Event')->last_sql); $this->assert_sql_has("WHERE id IN(?,?,?)", Table::load('Host')->last_sql); } @@ -653,7 +653,7 @@ public function test_eager_loading_belongs_to() $this->assert_equals($event->venue_id, $event->venue->id); } - $this->assert_sql_has("WHERE id IN(?,?,?,?,?)", Table::load('Venue')->last_sql); + $this->assert_sql_has("WHERE " . Venue::table()->column_name_for('id') . " IN(?,?,?,?,?)", Table::load('Venue')->last_sql); } public function test_eager_loading_belongs_to_array_of_includes() @@ -667,7 +667,7 @@ public function test_eager_loading_belongs_to_array_of_includes() $this->assert_sql_has("WHERE id IN(?,?,?,?,?)", Table::load('Event')->last_sql); $this->assert_sql_has("WHERE id IN(?,?,?,?,?)", Table::load('Host')->last_sql); - $this->assert_sql_has("WHERE id IN(?,?,?,?,?)", Table::load('Venue')->last_sql); + $this->assert_sql_has("WHERE " . Venue::table()->column_name_for('id') . " IN(?,?,?,?,?)", Table::load('Venue')->last_sql); } public function test_eager_loading_belongs_to_nested() @@ -700,7 +700,7 @@ public function test_eager_loading_belongs_to_with_no_related_rows() } $this->assert_sql_has("WHERE id IN(?,?)", Table::load('Event')->last_sql); - $this->assert_sql_has("WHERE id IN(?,?)", Table::load('Venue')->last_sql); + $this->assert_sql_has("WHERE " . Venue::table()->column_name_for('id') . " IN(?,?)", Table::load('Venue')->last_sql); } public function test_eager_loading_clones_related_objects() diff --git a/test/SQLBuilderTest.php b/test/SQLBuilderTest.php index ea3215696..6ea28cca7 100644 --- a/test/SQLBuilderTest.php +++ b/test/SQLBuilderTest.php @@ -74,8 +74,9 @@ public function test_where_with_hash_and_array() public function test_gh134_where_with_hash_and_null() { $this->sql->where(array('id' => 1, 'name' => null)); - $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name IS ?", (string)$this->sql); - $this->assert_equals(array(1, null), $this->sql->get_where_values()); + $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name IS NULL", (string)$this->sql); + // the null is rendered as a literal IS NULL, not bound + $this->assert_equals(array(1), $this->sql->get_where_values()); } public function test_where_with_null() diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index cd3b00225..ca2829a5e 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -152,7 +152,7 @@ public function test_validates_uniqueness_of_works_with_mysql_reserved_word_as_c $valuestore = ValuestoreValidations::create(['key' => 'GA_KEY', 'value' => 'UA-1234567-2']); $this->assert_equals(["Key must be unique"], $valuestore->errors->full_messages()); - $this->assert_equals(1, ValuestoreValidations::count(['conditions' => "`key`='GA_KEY'"])); + $this->assert_equals(1, ValuestoreValidations::count(['conditions' => $this->conn->quote_name('key') . "='GA_KEY'"])); } public function test_get_validation_rules() diff --git a/test/helpers/config.php b/test/helpers/config.php index 68c46f6d8..b58c5e2f0 100644 --- a/test/helpers/config.php +++ b/test/helpers/config.php @@ -22,7 +22,7 @@ use TestHelpers\DatabaseTest; // whether or not to run the slow non-crucial tests -$GLOBALS['slow_tests'] = false; +$GLOBALS['slow_tests'] = getenv('PHPAR_SLOW_TESTS') === 'true'; // whether or not to show warnings when Log or Memcache is missing $GLOBALS['show_warnings'] = true; @@ -39,15 +39,8 @@ 'pgsql' => getenv('PHPAR_PGSQL') ?: 'pgsql://test:test@127.0.0.1/test', 'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db')); - $cfg->set_default_connection('mysql'); - - for ($i = 0; $i < count($GLOBALS['argv']); ++$i) { - if ($GLOBALS['argv'][$i] == '--adapter') { - $cfg->set_default_connection($GLOBALS['argv'][$i + 1]); - } elseif ($GLOBALS['argv'][$i] == '--slow-tests') { - $GLOBALS['slow_tests'] = true; - } - } + // Adapter the model-level tests run against: mysql (default), pgsql or sqlite. + $cfg->set_default_connection(getenv('PHPAR_ADAPTER') ?: 'mysql'); if (class_exists('Log_file')) { // PEAR Log installed $logger = new Log_file(dirname(__FILE__) . '/../log/query.log', 'ident', array('mode' => 0664, 'timeFormat' => '%Y-%m-%d %H:%M:%S')); diff --git a/test/sql/pgsql.sql b/test/sql/pgsql.sql index 356647e05..6b5462c0b 100644 --- a/test/sql/pgsql.sql +++ b/test/sql/pgsql.sql @@ -39,7 +39,7 @@ CREATE TABLE venues ( CREATE TABLE events ( id SERIAL PRIMARY KEY, venue_id int NULL, - host_id int NOT NULL, + host_id int NULL, title varchar(60) NOT NULL, description varchar(10), type varchar(15) default NULL diff --git a/test/sql/sqlite.sql b/test/sql/sqlite.sql index a3b4fe9cb..707e76bdb 100644 --- a/test/sql/sqlite.sql +++ b/test/sql/sqlite.sql @@ -39,7 +39,7 @@ CREATE TABLE venues ( CREATE TABLE events ( id INTEGER NOT NULL PRIMARY KEY, venue_id int NULL, - host_id int NOT NULL, + host_id int NULL, title varchar(60) NOT NULL, description varchar(10), type varchar(15) default NULL From ccf8b9fe99dca9252865c240cecd96ec1fe0da0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 01:25:19 +0200 Subject: [PATCH 09/12] Run the PostgreSQL matrix rows with PostgreSQL as the default adapter The three PostgreSQL rows (14, 16, 18) now set PHPAR_ADAPTER=pgsql so the model-level tests run against each supported PostgreSQL version. The remaining rows keep MySQL as the default, so MySQL coverage is unchanged. Document PHPAR_ADAPTER in CONTRIBUTING.md. Bump actions/checkout from v4 to v7: v4 runs on Node 20, which the hosted runners are retiring. shivammathur/setup-php stays on v2, its current major. --- .github/workflows/tests.yml | 16 ++++++++++------ CONTRIBUTING.md | 6 ++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3cdf6cbd..fae6cf345 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,22 +6,25 @@ on: jobs: phpunit: - name: PHP ${{ matrix.php }} / MySQL ${{ matrix.mysql }} / PostgreSQL ${{ matrix.pgsql }} + name: PHP ${{ matrix.php }} / MySQL ${{ 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. - # SQLite is bundled with each PHP build and is exercised in every job. + # 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' } - # PostgreSQL axis - - { php: '8.4', mysql: '8.4', pgsql: '14' } - - { php: '8.4', mysql: '8.4', pgsql: '16' } + # 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' } @@ -72,7 +75,7 @@ jobs: - 11211:11211 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 @@ -87,5 +90,6 @@ jobs: - 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d0148e76..d274c18fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,8 @@ vendor/bin/phpunit --display-skipped 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. +`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. 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: @@ -60,4 +61,5 @@ You also need the PHP extensions `pdo_mysql`, `pdo_pgsql`, `pdo_sqlite` and `mem `.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. Update the matrix when a PHP or database version reaches end of life. +single axis at a time. The PostgreSQL rows run the model-level tests with PostgreSQL as the default +adapter. Update the matrix when a PHP or database version reaches end of life. From 3b176663a39f7306eccaaf59df7746f53826cb02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 01:37:19 +0200 Subject: [PATCH 10/12] Test MariaDB 10.11, 11.8 and 12.3 in the matrix MariaDB reaches the library through the MySQL adapter and is what Debian, Ubuntu and RHEL ship as their MySQL, yet it diverges from MySQL 8 where the adapter is sensitive: no time zone offsets in datetime literals, integer display widths still reported, a looser default sql_mode and a different version string. Three rows cover 10.11 (Debian 12, Ubuntu 24.04, RHEL 9/10), 11.8 (Debian 13) and the current 12.3 LTS. They reuse the MySQL service definition: the image name comes from the matrix, and the health check uses the image's healthcheck.sh because the 12.x image no longer ships mysqladmin. compose.yaml gets the same three servers for local runs. --- .github/workflows/tests.yml | 12 +++++++++--- CONTRIBUTING.md | 9 +++++---- compose.yaml | 12 ++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fae6cf345..1f847eba4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: jobs: phpunit: - name: PHP ${{ matrix.php }} / MySQL ${{ matrix.mysql }} / PostgreSQL ${{ matrix.pgsql }} / default ${{ matrix.adapter || 'mysql' }} + 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 @@ -21,6 +21,11 @@ jobs: # 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 } @@ -35,7 +40,8 @@ jobs: # network-backed disk. The databases are throwaway, so nothing is lost. services: mysql: - image: mysql:${{ matrix.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 @@ -45,7 +51,7 @@ jobs: - 3306:3306 options: >- --tmpfs /var/lib/mysql:rw - --health-cmd "mysqladmin ping -h 127.0.0.1 -uroot -proot" + --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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d274c18fb..7a9c470b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,8 +51,9 @@ 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, `pgsql14` is 54314, -`pgsql16` is 54316 and `pgsql18` is 54318. The containers keep their data on tmpfs and skip fsyncs, so +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`. @@ -61,5 +62,5 @@ You also need the PHP extensions `pdo_mysql`, `pdo_pgsql`, `pdo_sqlite` and `mem `.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. The PostgreSQL rows run the model-level tests with PostgreSQL as the default -adapter. Update the matrix when a PHP or database version reaches end of life. +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. Update the matrix when a PHP or database version reaches end of life. diff --git a/compose.yaml b/compose.yaml index 8cde06ad4..d86d0386f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -44,6 +44,18 @@ services: <<: *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 From f6c829652bb66bd42749fb335f7f1ab69fa2c695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 02:24:54 +0200 Subject: [PATCH 11/12] Modernise syntax: short arrays, no aliases, explicit visibility Mechanical style update, no behaviour change: - array() becomes [] everywhere, in code and in docblock examples, README and the examples directory. Three docblock examples had a missing parenthesis in the original text and were fixed by hand. - list() destructuring becomes [...]. - Function aliases join, sizeof and is_integer become implode, count and is_int. - Static properties that relied on the implicit public get it spelled out. - A commented-out duplicate has_one definition in the Author test model is removed. phpcs.xml enforces the new state: Generic.Arrays.DisallowLongArraySyntax, Generic.PHP.ForbiddenFunctions with the alias table, and the PSR-12 property visibility rule is no longer excluded. The examples directory joins lib and test in the checked paths. --- README.md | 10 +- examples/orders/models/Order.php | 57 ++--- examples/orders/models/Payment.php | 10 +- examples/orders/models/Person.php | 16 +- examples/orders/orders.php | 83 ++++---- examples/simple/simple.php | 13 +- examples/simple/simple_with_options.php | 29 ++- lib/AbstractRelationship.php | 30 +-- lib/Adapters/MysqlAdapter.php | 4 +- lib/Adapters/PgsqlAdapter.php | 6 +- lib/Adapters/SqliteAdapter.php | 4 +- lib/Cache.php | 12 +- lib/CallBack.php | 26 +-- lib/Column.php | 2 +- lib/Config.php | 26 +-- lib/Connection.php | 14 +- lib/ConnectionManager.php | 2 +- lib/DateTime.php | 4 +- lib/Errors.php | 32 +-- lib/Exceptions/DatabaseException.php | 4 +- lib/Expressions.php | 12 +- lib/Inflector.php | 4 +- lib/Model.php | 194 +++++++++--------- lib/Reflections.php | 4 +- lib/Relationships/HasMany.php | 40 ++-- lib/Relationships/HasOne.php | 2 +- lib/SQLBuilder.php | 12 +- lib/Serialization.php | 46 ++--- lib/Singleton.php | 2 +- lib/Table.php | 48 ++--- lib/Utils.php | 22 +- lib/Validations.php | 106 +++++----- phpcs.xml | 24 ++- test/ActiveRecordCacheTest.php | 2 +- test/ActiveRecordFindTest.php | 96 ++++----- test/ActiveRecordTest.php | 62 +++--- test/ActiveRecordWriteTest.php | 72 +++---- test/CacheModelTest.php | 22 +- test/ColumnTest.php | 4 +- test/ConfigTest.php | 4 +- test/DateTimeTest.php | 2 +- test/ExpressionsTest.php | 44 ++-- test/HasManyThroughTest.php | 20 +- test/ModelCallbackTest.php | 20 +- test/PgsqlAdapterTest.php | 2 +- test/RelationshipTest.php | 2 +- test/SQLBuilderTest.php | 70 +++---- test/SqliteAdapterTest.php | 2 +- test/ValidatesFormatOfTest.php | 4 +- test/ValidatesInclusionAndExclusionOfTest.php | 4 +- test/ValidatesLengthOfTest.php | 8 +- test/ValidatesNumericalityOfTest.php | 16 +- test/ValidatesPresenceOfTest.php | 8 +- test/ValidationsTest.php | 14 +- test/helpers/AdapterTest.php | 22 +- test/helpers/DatabaseLoader.php | 8 +- test/helpers/DatabaseTest.php | 10 +- .../SnakeCase_PHPUnit_Framework_TestCase.php | 8 +- test/helpers/config.php | 6 +- test/models/Amenity.php | 6 +- test/models/Author.php | 12 +- test/models/AuthorAttrAccessible.php | 12 +- test/models/AwesomePerson.php | 2 +- test/models/Biz/Newsletter.php | 8 +- test/models/Biz/User.php | 8 +- test/models/Biz/UserNewsletter.php | 8 +- test/models/Book.php | 6 +- test/models/BookAttrAccessible.php | 8 +- test/models/BookAttrProtected.php | 8 +- test/models/Employee.php | 2 +- test/models/Event.php | 4 +- test/models/Host.php | 2 +- test/models/JoinAuthor.php | 4 +- test/models/JoinBook.php | 4 +- test/models/NamespaceTest/Book.php | 18 +- .../NamespaceTest/SubNamespaceTest/Page.php | 6 +- test/models/Position.php | 2 +- test/models/Property.php | 6 +- test/models/PropertyAmenity.php | 6 +- test/models/Publisher.php | 8 +- test/models/RmBldg.php | 14 +- test/models/Venue.php | 10 +- test/models/VenueAfterCreate.php | 4 +- test/models/VenueCB.php | 16 +- 84 files changed, 816 insertions(+), 800 deletions(-) diff --git a/README.md b/README.md index a539407f6..05f0cf99c 100755 --- a/README.md +++ b/README.md @@ -64,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' - ) + ] ); }); ``` @@ -79,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' - ) + ] ); ``` @@ -121,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/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 ca8305a86..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,12 +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 = []) { // 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 = array(); + $values = []; $options = $this->options; $inflector = Inflector::instance(); $query_key = $query_keys[0]; @@ -140,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) { @@ -186,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); @@ -223,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); @@ -236,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); @@ -307,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)); @@ -323,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); 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/PgsqlAdapter.php b/lib/Adapters/PgsqlAdapter.php index ae974e733..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() { @@ -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 99601f322..7196ea95f 100644 --- a/lib/Adapters/SqliteAdapter.php +++ b/lib/Adapters/SqliteAdapter.php @@ -56,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 fe648f1dc..8f639135a 100644 --- a/lib/Cache.php +++ b/lib/Cache.php @@ -13,8 +13,8 @@ */ class Cache { - static $adapter = null; - static $options = array(); + public static $adapter = null; + public static $options = []; /** * Initializes the cache. @@ -28,10 +28,10 @@ class Cache * * Ex: * $cfg_ar = ActiveRecord\Config::instance(); - * $cfg_ar->set_cache('memcached://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'. @@ -41,7 +41,7 @@ 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); @@ -56,7 +56,7 @@ public static function initialize($url, $options = array()) 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/CallBack.php b/lib/CallBack.php index eb248a80c..65f1ed984 100644 --- a/lib/CallBack.php +++ b/lib/CallBack.php @@ -38,8 +38,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 ce25f8c29..d54901a4e 100644 --- a/lib/Connection.php +++ b/lib/Connection.php @@ -83,11 +83,11 @@ abstract class Connection * 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. @@ -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 ad8b70ccf..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,7 +124,7 @@ public function to_s($substitute = false, &$options = null) private function build_sql_from_hash(&$hash, $glue) { $sql = $g = ""; - $values = array(); + $values = []; foreach ($hash as $name => $value) { if ($this->connection) { @@ -144,7 +144,7 @@ private function build_sql_from_hash(&$hash, $glue) $g = $glue; } - return array($sql, $values); + return [$sql, $values]; } private function substitute(&$values, $substitute, $pos, $parameter_index) @@ -169,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 f2776a369..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,14 +631,14 @@ 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. @@ -657,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)) { @@ -917,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: @@ -943,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(); @@ -952,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); } @@ -978,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: @@ -999,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(); @@ -1009,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); } @@ -1057,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() { @@ -1068,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; @@ -1091,7 +1091,7 @@ private function validate() $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; } @@ -1101,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); } @@ -1163,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) @@ -1192,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) { @@ -1203,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(); @@ -1268,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; } @@ -1287,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(); @@ -1299,7 +1299,7 @@ public function reload() public function __clone() { - $this->__relationships = array(); + $this->__relationships = []; $this->reset_dirty(); return $this; } @@ -1344,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 @@ -1412,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(); @@ -1445,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 @@ -1476,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 @@ -1523,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: @@ -1549,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'
  • @@ -1585,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']); } @@ -1624,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) { @@ -1680,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) { @@ -1696,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'"); * * @@ -1736,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); @@ -1753,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; } @@ -1771,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]; @@ -1786,7 +1786,7 @@ public static function extract_and_validate_options(array &$array) throw $e; } - $options = array('conditions' => $last); + $options = ['conditions' => $last]; } } return $options; @@ -1799,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); } @@ -1811,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); } @@ -1825,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 * * @@ -1836,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); } @@ -1848,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); } @@ -1895,7 +1895,7 @@ private function invoke_callback($method_name, $must_exist = true) * * YourModel::transaction(function() * { - * YourModel::create(array("name" => "blah")); + * YourModel::create(["name" => "blah"]); * }); * * @@ -1906,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 44300eb34..b86529841 100644 --- a/lib/Reflections.php +++ b/lib/Reflections.php @@ -21,7 +21,7 @@ class Reflections extends Singleton * * @var array */ - private $reflections = array(); + private $reflections = []; /** * Instantiates a new ReflectionClass for the given class. @@ -97,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 24cfd0325..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) { @@ -349,7 +349,7 @@ private function build_delete() private function build_insert() { - $keys = join(',', $this->quoted_key_names()); + $keys = implode(',', $this->quoted_key_names()); if ($this->sequence) { $sql = @@ -399,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 892e3662a..264e68c48 100644 --- a/lib/Serialization.php +++ b/lib/Serialization.php @@ -30,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 @@ -64,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 @@ -164,7 +164,7 @@ private function check_include() foreach ($this->options['include'] as $association => $options) { if (!is_array($options)) { $association = $options; - $options = array(); + $options = []; } try { @@ -177,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); @@ -201,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 5c7da7fb7..592d1e7b0 100644 --- a/lib/Table.php +++ b/lib/Table.php @@ -89,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 = []; } } @@ -109,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) @@ -186,10 +186,10 @@ 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']); @@ -244,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)); @@ -286,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); @@ -443,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)) { @@ -457,7 +457,7 @@ private function map_names(&$hash, &$map) private function map_column_names($hash) { - $ret = array(); + $ret = []; foreach ($hash as $name => $value) { $ret[$this->column_name_for($name)] = $value; @@ -473,7 +473,7 @@ private function &process_data($hash) } $date_class = Config::instance()->get_date_class(); - $ret = array(); + $ret = []; foreach ($hash as $name => $value) { $name = $this->column_name_for($name); @@ -495,9 +495,9 @@ private function &process_data($hash) private function set_primary_key() { if (($pk = $this->class->getStaticPropertyValue('pk', null)) || ($pk = $this->class->getStaticPropertyValue('primary_key', null))) { - $this->pk = is_array($pk) ? $pk : array($pk); + $this->pk = is_array($pk) ? $pk : [$pk]; } else { - $this->pk = array(); + $this->pk = []; foreach ($this->columns as $c) { if ($c->pk) { @@ -566,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': @@ -597,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; @@ -620,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)) { @@ -644,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) * *
      *
    • is: attribute should be exactly n characters long
    • - *
    • in/within: attribute should be within an range array(min,max)
    • + *
    • in/within: attribute should be within an range [min,max]
    • *
    • maximum/minimum: attribute should not be above/below respectively
    • *
    • message: custome error message
    • *
    • allow_blank: allow blank strings
    • @@ -455,18 +455,18 @@ public function validates_format_of($attrs) */ public function validates_length_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array( + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, [ 'too_long' => Errors::$DEFAULT_ERROR_MESSAGES['too_long'], 'too_short' => Errors::$DEFAULT_ERROR_MESSAGES['too_short'], 'wrong_length' => Errors::$DEFAULT_ERROR_MESSAGES['wrong_length'] - )); + ]); foreach ($attrs as $attr) { $options = array_merge($configuration, $attr); $range_options = array_intersect(array_keys(self::$ALL_RANGE_OPTIONS), array_keys($attr)); sort($range_options); - switch (sizeof($range_options)) { + switch (count($range_options)) { case 0: throw new ValidationsArgumentError('Range unspecified. Specify the [within], [maximum], or [is] option.'); @@ -488,7 +488,7 @@ public function validates_length_of($attrs) if (!(Utils::is_a('range', $range))) { throw new ValidationsArgumentError("$range_options[0] must be an array composing a range of numbers with key [0] being less than key [1]"); } - $range_options = array('minimum', 'maximum'); + $range_options = ['minimum', 'maximum']; $attr['minimum'] = $range[0]; $attr['maximum'] = $range[1]; } @@ -504,7 +504,7 @@ public function validates_length_of($attrs) } if (!($range_option == 'maximum' && is_null($this->model->$attribute))) { - $messageOptions = array('is' => 'wrong_length', 'minimum' => 'too_short', 'maximum' => 'too_long'); + $messageOptions = ['is' => 'wrong_length', 'minimum' => 'too_short', 'maximum' => 'too_long']; if (isset($options['message'])) { $message = $options['message']; @@ -539,10 +539,10 @@ public function validates_length_of($attrs) * * * class Person extends ActiveRecord\Model { - * static $validates_uniqueness_of = array( - * array('name'), - * array(array('blah','bleh'), 'message' => 'blech') - * ); + * static $validates_uniqueness_of = [ + * ['name'], + * [['blah','bleh'], 'message' => 'blech'] + * ]; * } * * @@ -559,9 +559,9 @@ public function validates_length_of($attrs) */ public function validates_uniqueness_of($attrs) { - $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array( + $configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, [ 'message' => Errors::$DEFAULT_ERROR_MESSAGES['unique'] - )); + ]); // Retrieve connection from model for quote_name method $connection = $this->klass->getMethod('connection')->invoke(null); @@ -571,15 +571,15 @@ public function validates_uniqueness_of($attrs) $pk_value = $this->model->{$pk[0]}; if (is_array($options[0])) { - $add_record = join("_and_", $options[0]); + $add_record = implode("_and_", $options[0]); $fields = $options[0]; } else { $add_record = $options[0]; - $fields = array($options[0]); + $fields = [$options[0]]; } $sql = ""; - $conditions = array(""); + $conditions = [""]; $pk_quoted = $connection->quote_name($pk[0]); if ($pk_value === null) { $sql = "{$pk_quoted} IS NOT NULL"; @@ -597,7 +597,7 @@ public function validates_uniqueness_of($attrs) $conditions[0] = $sql; - if ($this->model->exists(array('conditions' => $conditions))) { + if ($this->model->exists(['conditions' => $conditions])) { $this->record->add($add_record, $options['message']); } } diff --git a/phpcs.xml b/phpcs.xml index 46ce5a88a..4ae8895ab 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,9 +1,10 @@ - The coding standard for PHP_CodeSniffer itself. + The coding standard for PHP ActiveRecord: PSR-12 plus modern PHP syntax. lib test + examples @@ -24,8 +25,25 @@ - - + + + + + + + + + + + + + + + + + + + diff --git a/test/ActiveRecordCacheTest.php b/test/ActiveRecordCacheTest.php index 7817bc9a9..df3917206 100644 --- a/test/ActiveRecordCacheTest.php +++ b/test/ActiveRecordCacheTest.php @@ -38,7 +38,7 @@ public function test_default_expire() public function test_explicit_default_expire() { - Config::instance()->set_cache('memcached://localhost', array('expire' => 1)); + Config::instance()->set_cache('memcached://localhost', ['expire' => 1]); $this->assert_equals(1, Cache::$options['expire']); } diff --git a/test/ActiveRecordFindTest.php b/test/ActiveRecordFindTest.php index 43dea70db..474d40999 100644 --- a/test/ActiveRecordFindTest.php +++ b/test/ActiveRecordFindTest.php @@ -44,7 +44,7 @@ public function test_find_by_multiple_pk_with_partial_match() public function test_find_by_pk_with_options() { - $author = Author::find(3, array('order' => 'name')); + $author = Author::find(3, ['order' => 'name']); $this->assert_equals(3, $author->id); $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name') !== false); } @@ -59,7 +59,7 @@ public function test_find_by_pk_array() public function test_find_by_pk_array_with_options() { - $authors = Author::find(1, '2', array('order' => 'name')); + $authors = Author::find(1, '2', ['order' => 'name']); $this->assert_equals(2, count($authors)); $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name') !== false); } @@ -72,32 +72,32 @@ public function test_find_nothing_with_sql_in_string() public function test_find_all() { - $authors = Author::find('all', array('conditions' => array('author_id IN(?)',array(1,2,3)))); + $authors = Author::find('all', ['conditions' => ['author_id IN(?)',[1,2,3]]]); $this->assert_true(count($authors) >= 3); } public function test_find_all_with_no_bind_values() { - $authors = Author::find('all', array('conditions' => array('author_id IN(1,2,3)'))); + $authors = Author::find('all', ['conditions' => ['author_id IN(1,2,3)']]); $this->assert_equals(1, $authors[0]->author_id); } public function test_find_all_with_empty_array_bind_value_throws_exception() { $this->expectException(DatabaseException::class); - $authors = Author::find('all', array('conditions' => array('author_id IN(?)', array()))); + $authors = Author::find('all', ['conditions' => ['author_id IN(?)', []]]); $this->assertCount(0, $authors); } public function test_find_hash_using_alias() { - $venues = Venue::all(array('conditions' => array('marquee' => 'Warner Theatre', 'city' => array('Washington','New York')))); + $venues = Venue::all(['conditions' => ['marquee' => 'Warner Theatre', 'city' => ['Washington','New York']]]); $this->assert_true(count($venues) >= 1); } public function test_find_hash_using_alias_with_null() { - $venues = Venue::all(array('conditions' => array('marquee' => null))); + $venues = Venue::all(['conditions' => ['marquee' => null]]); $this->assert_equals(0, count($venues)); } @@ -108,13 +108,13 @@ public function test_dynamic_finder_using_alias() public function test_find_all_hash() { - $books = Book::find('all', array('conditions' => array('author_id' => 1))); + $books = Book::find('all', ['conditions' => ['author_id' => 1]]); $this->assert_true(count($books) > 0); } public function test_find_all_hash_with_order() { - $books = Book::find('all', array('conditions' => array('author_id' => 1), 'order' => 'name DESC')); + $books = Book::find('all', ['conditions' => ['author_id' => 1], 'order' => 'name DESC']); $this->assert_true(count($books) > 0); } @@ -126,20 +126,20 @@ public function test_find_all_no_args() public function test_find_all_no_results() { - $authors = Author::find('all', array('conditions' => array('author_id IN(11111111111,22222222222,333333333333)'))); - $this->assert_equals(array(), $authors); + $authors = Author::find('all', ['conditions' => ['author_id IN(11111111111,22222222222,333333333333)']]); + $this->assert_equals([], $authors); } public function test_find_first() { - $author = Author::find('first', array('conditions' => array('author_id IN(?)', array(1,2,3)))); + $author = Author::find('first', ['conditions' => ['author_id IN(?)', [1,2,3]]]); $this->assert_equals(1, $author->author_id); $this->assert_equals('Tito', $author->name); } public function test_find_first_no_results() { - $this->assert_null(Author::find('first', array('conditions' => 'author_id=1111111'))); + $this->assert_null(Author::find('first', ['conditions' => 'author_id=1111111'])); } public function test_find_first_using_pk() @@ -150,13 +150,13 @@ public function test_find_first_using_pk() public function test_find_first_with_conditions_as_string() { - $author = Author::find('first', array('conditions' => 'author_id=3')); + $author = Author::find('first', ['conditions' => 'author_id=3']); $this->assert_equals(3, $author->author_id); } public function test_find_all_with_conditions_as_string() { - $author = Author::find('all', array('conditions' => 'author_id in(2,3)')); + $author = Author::find('all', ['conditions' => 'author_id in(2,3)']); $this->assert_equals(2, count($author)); } @@ -169,13 +169,13 @@ public function test_find_by_sql() public function test_find_by_sqltakes_values_array() { - $author = Author::find_by_sql("SELECT * FROM authors WHERE author_id=?", array(1)); + $author = Author::find_by_sql("SELECT * FROM authors WHERE author_id=?", [1]); $this->assert_not_null($author); } public function test_find_with_conditions() { - $author = Author::find(array('conditions' => array('author_id=? and name=?', 1, 'Tito'))); + $author = Author::find(['conditions' => ['author_id=? and name=?', 1, 'Tito']]); $this->assert_equals(1, $author->author_id); } @@ -188,14 +188,14 @@ public function test_find_last() public function test_find_last_using_string_condition() { - $author = Author::find('last', array('conditions' => 'author_id IN(1,2,3,4)')); + $author = Author::find('last', ['conditions' => 'author_id IN(1,2,3,4)']); $this->assert_equals(4, $author->author_id); $this->assert_equals('Uncle Bob', $author->name); } public function test_limit_before_order() { - $authors = Author::all(array('limit' => 2, 'order' => 'author_id desc', 'conditions' => 'author_id in(1,2)')); + $authors = Author::all(['limit' => 2, 'order' => 'author_id desc', 'conditions' => 'author_id in(1,2)']); $this->assert_equals(2, $authors[0]->author_id); $this->assert_equals(1, $authors[1]->author_id); } @@ -226,35 +226,35 @@ public function test_fetch_all() public function test_count() { $this->assert_equals(1, Author::count(1)); - $this->assert_equals(2, Author::count(array(1,2))); + $this->assert_equals(2, Author::count([1,2])); $this->assert_true(Author::count() > 1); - $this->assert_equals(0, Author::count(array('conditions' => 'author_id=99999999999999'))); - $this->assert_equals(2, Author::count(array('conditions' => 'author_id=1 or author_id=2'))); - $this->assert_equals(1, Author::count(array('name' => 'Tito', 'author_id' => 1))); + $this->assert_equals(0, Author::count(['conditions' => 'author_id=99999999999999'])); + $this->assert_equals(2, Author::count(['conditions' => 'author_id=1 or author_id=2'])); + $this->assert_equals(1, Author::count(['name' => 'Tito', 'author_id' => 1])); } public function test_gh149_empty_count() { $total = Author::count(); $this->assert_equals($total, Author::count(null)); - $this->assert_equals($total, Author::count(array())); + $this->assert_equals($total, Author::count([])); } public function test_exists() { $this->assert_true(Author::exists(1)); - $this->assert_true(Author::exists(array('conditions' => 'author_id=1'))); - $this->assert_true(Author::exists(array('conditions' => array('author_id=? and name=?', 1, 'Tito')))); + $this->assert_true(Author::exists(['conditions' => 'author_id=1'])); + $this->assert_true(Author::exists(['conditions' => ['author_id=? and name=?', 1, 'Tito']])); $this->assert_false(Author::exists(9999999)); - $this->assert_false(Author::exists(array('conditions' => 'author_id=999999'))); + $this->assert_false(Author::exists(['conditions' => 'author_id=999999'])); } public function test_find_by_call_static() { $this->assert_equals('Tito', Author::find_by_name('Tito')->name); $this->assert_equals('Tito', Author::find_by_author_id_and_name(1, 'Tito')->name); - $this->assert_equals('George W. Bush', Author::find_by_author_id_or_name(2, 'Tito', array('order' => 'author_id desc'))->name); - $this->assert_equals('Tito', Author::find_by_name(array('Tito','George W. Bush'), array('order' => 'name desc'))->name); + $this->assert_equals('George W. Bush', Author::find_by_author_id_or_name(2, 'Tito', ['order' => 'author_id desc'])->name); + $this->assert_equals('Tito', Author::find_by_name(['Tito','George W. Bush'], ['order' => 'name desc'])->name); } public function test_find_by_call_static_no_results() @@ -275,7 +275,7 @@ public function test_find_all_by_call_static() $this->assert_equals('Tito', $x[0]->name); $this->assert_equals(1, count($x)); - $x = Author::find_all_by_author_id_or_name(2, 'Tito', array('order' => 'name asc')); + $x = Author::find_all_by_author_id_or_name(2, 'Tito', ['order' => 'name asc']); $this->assert_equals(2, count($x)); $this->assert_equals('George W. Bush', $x[0]->name); } @@ -288,7 +288,7 @@ public function test_find_all_by_call_static_no_results() public function test_find_all_by_call_static_with_array_values_and_options() { - $author = Author::find_all_by_name(array('Tito','Bill Clinton'), array('order' => 'name desc')); + $author = Author::find_all_by_name(['Tito','Bill Clinton'], ['order' => 'name desc']); $this->assert_equals('Tito', $author[0]->name); $this->assert_equals('Bill Clinton', $author[1]->name); } @@ -301,7 +301,7 @@ public function test_find_all_by_call_static_undefined_method() public function test_find_all_takes_limit_options() { - $authors = Author::all(array('limit' => 1, 'offset' => 2, 'order' => 'name desc')); + $authors = Author::all(['limit' => 1, 'offset' => 2, 'order' => 'name desc']); $this->assert_equals('George W. Bush', $authors[0]->name); } @@ -313,14 +313,14 @@ public function test_find_by_call_static_with_invalid_field_name() public function test_find_with_select() { - $author = Author::first(array('select' => 'name, 123 as bubba', 'order' => 'name desc')); + $author = Author::first(['select' => 'name, 123 as bubba', 'order' => 'name desc']); $this->assert_equals('Uncle Bob', $author->name); $this->assert_equals(123, $author->bubba); } public function test_find_with_select_non_selected_fields_should_not_have_attributes() { - $author = Author::first(array('select' => 'name, 123 as bubba')); + $author = Author::first(['select' => 'name, 123 as bubba']); try { $author->id; $this->fail('expected ActiveRecord\UndefinedPropertyExecption'); @@ -331,28 +331,28 @@ public function test_find_with_select_non_selected_fields_should_not_have_attrib public function test_joins_on_model_with_association_and_explicit_joins() { - JoinBook::$belongs_to = array(array('author', 'foreign_key' => 'author_id')); - JoinBook::first(array('joins' => array('author','LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)'))); + JoinBook::$belongs_to = [['author', 'foreign_key' => 'author_id']]; + JoinBook::first(['joins' => ['author','LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)']]); $this->assert_sql_has('INNER JOIN authors ON(books.author_id = authors.author_id)', JoinBook::table()->last_sql); $this->assert_sql_has('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)', JoinBook::table()->last_sql); } public function test_joins_on_model_with_explicit_joins() { - JoinBook::first(array('joins' => array('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)'))); + JoinBook::first(['joins' => ['LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)']]); $this->assert_sql_has('LEFT JOIN authors a ON(books.secondary_author_id=a.author_id)', JoinBook::table()->last_sql); } public function test_group() { - $venues = Venue::all(array('select' => 'state', 'group' => 'state')); + $venues = Venue::all(['select' => 'state', 'group' => 'state']); $this->assert_true(count($venues) > 0); $this->assert_sql_has('GROUP BY state', Table::load('Venue')->last_sql); } public function test_group_with_order_and_limit_and_having() { - $venues = Venue::all(array('select' => 'state', 'group' => 'state', 'having' => 'length(state) = 2', 'order' => 'state', 'limit' => 2)); + $venues = Venue::all(['select' => 'state', 'group' => 'state', 'having' => 'length(state) = 2', 'order' => 'state', 'limit' => 2]); $this->assert_true(count($venues) > 0); $this->assert_sql_has($this->conn->limit('SELECT state FROM venues GROUP BY state HAVING length(state) = 2 ORDER BY state', null, 2), Venue::table()->last_sql); } @@ -365,36 +365,36 @@ public function test_escape_quotes() public function test_from() { - $author = Author::find('first', array('from' => 'books', 'order' => 'author_id asc')); + $author = Author::find('first', ['from' => 'books', 'order' => 'author_id asc']); $this->assert_true($author instanceof Author); $this->assert_not_null($author->book_id); - $author = Author::find('first', array('from' => 'authors', 'order' => 'author_id asc')); + $author = Author::find('first', ['from' => 'authors', 'order' => 'author_id asc']); $this->assert_true($author instanceof Author); $this->assert_equals(1, $author->id); } public function test_having() { - $author = Author::first(array( + $author = Author::first([ 'select' => 'date(created_at) as created_at', 'group' => 'date(created_at)', - 'having' => "date(created_at) > '2009-01-01'")); + 'having' => "date(created_at) > '2009-01-01'"]); $this->assert_sql_has("GROUP BY date(created_at) HAVING date(created_at) > '2009-01-01'", Author::table()->last_sql); } public function test_from_with_invalid_table() { $this->expectException(DatabaseException::class); - $author = Author::find('first', array('from' => 'wrong_authors_table')); + $author = Author::find('first', ['from' => 'wrong_authors_table']); } public function test_find_with_hash() { - $this->assert_not_null(Author::find(array('name' => 'Tito'))); - $this->assert_not_null(Author::find('first', array('name' => 'Tito'))); - $this->assert_equals(1, count(Author::find('all', array('name' => 'Tito')))); - $this->assert_equals(1, count(Author::all(array('name' => 'Tito')))); + $this->assert_not_null(Author::find(['name' => 'Tito'])); + $this->assert_not_null(Author::find('first', ['name' => 'Tito'])); + $this->assert_equals(1, count(Author::find('all', ['name' => 'Tito']))); + $this->assert_equals(1, count(Author::all(['name' => 'Tito']))); } public function test_find_or_create_by_on_existing_record() diff --git a/test/ActiveRecordTest.php b/test/ActiveRecordTest.php index 7c3bbaf9e..7206e7172 100644 --- a/test/ActiveRecordTest.php +++ b/test/ActiveRecordTest.php @@ -30,14 +30,14 @@ public function test_options_is_not() $this->assert_false(Author::is_options_hash(null)); $this->assert_false(Author::is_options_hash('')); $this->assert_false(Author::is_options_hash('tito')); - $this->assert_false(Author::is_options_hash(array())); - $this->assert_false(Author::is_options_hash(array(1,2,3))); + $this->assert_false(Author::is_options_hash([])); + $this->assert_false(Author::is_options_hash([1,2,3])); } public function test_options_hash_with_unknown_keys() { $this->expectException(ActiveRecordException::class); - $this->assert_false(Author::is_options_hash(array('conditions' => 'blah', 'sharks' => 'laserz', 'dubya' => 'bush'))); + $this->assert_false(Author::is_options_hash(['conditions' => 'blah', 'sharks' => 'laserz', 'dubya' => 'bush'])); } public function test_options_is_hash() @@ -47,41 +47,41 @@ public function test_options_is_hash() public function test_extract_and_validate_options() { - $args = array('first',$this->options); + $args = ['first',$this->options]; $this->assert_equals($this->options, Author::extract_and_validate_options($args)); - $this->assert_equals(array('first'), $args); + $this->assert_equals(['first'], $args); } public function test_extract_and_validate_options_with_array_in_args() { - $args = array('first',array(1,2),$this->options); + $args = ['first',[1,2],$this->options]; $this->assert_equals($this->options, Author::extract_and_validate_options($args)); } public function test_extract_and_validate_options_removes_options_hash() { - $args = array('first',$this->options); + $args = ['first',$this->options]; Author::extract_and_validate_options($args); - $this->assert_equals(array('first'), $args); + $this->assert_equals(['first'], $args); } public function test_extract_and_validate_options_nope() { - $args = array('first'); - $this->assert_equals(array(), Author::extract_and_validate_options($args)); - $this->assert_equals(array('first'), $args); + $args = ['first']; + $this->assert_equals([], Author::extract_and_validate_options($args)); + $this->assert_equals(['first'], $args); } public function test_extract_and_validate_options_nope_because_wasnt_at_end() { - $args = array('first',$this->options,array(1,2)); - $this->assert_equals(array(), Author::extract_and_validate_options($args)); + $args = ['first',$this->options,[1,2]]; + $this->assert_equals([], Author::extract_and_validate_options($args)); } public function test_invalid_attribute() { $this->expectException(UndefinedPropertyException::class); - $author = Author::find('first', array('conditions' => 'author_id=1')); + $author = Author::find('first', ['conditions' => 'author_id=1']); $author->some_invalid_field_name; } @@ -89,7 +89,7 @@ public function test_invalid_attributes() { $book = Book::find(1); try { - $book->update_attributes(array('name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'something')); + $book->update_attributes(['name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'something']); } catch (UndefinedPropertyException $e) { $exceptions = explode("\r\n", $e->getMessage()); } @@ -109,7 +109,7 @@ public function test_getter_undefined_property_exception_includes_model_name() public function test_mass_assignment_undefined_property_exception_includes_model_name() { $this->assert_exception_message_contains("Author->this_better_not_exist", function () { - new Author(array("this_better_not_exist" => "hi")); + new Author(["this_better_not_exist" => "hi"]); }); } @@ -124,9 +124,9 @@ public function test_setter_undefined_property_exception_includes_model_name() public function test_get_values_for() { $book = Book::find_by_name('Ancient Art of Main Tanking'); - $ret = $book->get_values_for(array('book_id','author_id')); - $this->assert_equals(array('book_id','author_id'), array_keys($ret)); - $this->assert_equals(array(1,1), array_values($ret)); + $ret = $book->get_values_for(['book_id','author_id']); + $this->assert_equals(['book_id','author_id'], array_keys($ret)); + $this->assert_equals([1,1], array_values($ret)); } public function test_hyphenated_column_names_to_underscore() @@ -240,7 +240,7 @@ public function test_namespaced_relationship_associates_correctly() public function test_should_have_all_column_attributes_when_initializing_with_array() { - $author = new Author(array('name' => 'Tito')); + $author = new Author(['name' => 'Tito']); $this->assert_true(count(array_keys($author->attributes())) >= 9); } @@ -271,7 +271,7 @@ public function test_alias_attribute_setter() public function test_alias_from_mass_attributes() { - $venue = new Venue(array('marquee' => 'meme', 'id' => 123)); + $venue = new Venue(['marquee' => 'meme', 'id' => 123]); $this->assert_equals('meme', $venue->name); $this->assert_equals($venue->marquee, $venue->name); } @@ -283,7 +283,7 @@ public function test_gh18_isset_on_aliased_attribute() public function test_attr_accessible() { - $book = new BookAttrAccessible(array('name' => 'should not be set', 'author_id' => 1)); + $book = new BookAttrAccessible(['name' => 'should not be set', 'author_id' => 1]); $this->assert_null($book->name); $this->assert_equals(1, $book->author_id); $book->name = 'test'; @@ -292,7 +292,7 @@ public function test_attr_accessible() public function test_attr_protected() { - $book = new BookAttrAccessible(array('book_id' => 999)); + $book = new BookAttrAccessible(['book_id' => 999]); $this->assert_null($book->book_id); $book->book_id = 999; $this->assert_equals(999, $book->book_id); @@ -344,7 +344,7 @@ public function test_transaction_committed() { $original = Author::count(); $ret = Author::transaction(function () { - Author::create(array("name" => "blah")); + Author::create(["name" => "blah"]); }); $this->assert_equals($original + 1, Author::count()); $this->assert_true($ret); @@ -354,7 +354,7 @@ public function test_transaction_committed_when_returning_true() { $original = Author::count(); $ret = Author::transaction(function () { - Author::create(array("name" => "blah")); + Author::create(["name" => "blah"]); return true; }); $this->assert_equals($original + 1, Author::count()); @@ -366,7 +366,7 @@ public function test_transaction_rolledback_by_returning_false() $original = Author::count(); $ret = Author::transaction(function () { - Author::create(array("name" => "blah")); + Author::create(["name" => "blah"]); return false; }); @@ -381,7 +381,7 @@ public function test_transaction_rolledback_by_throwing_exception() try { Author::transaction(function () { - Author::create(array("name" => "blah")); + Author::create(["name" => "blah"]); throw new Exception("blah"); }); } catch (Exception $e) { @@ -529,7 +529,7 @@ public function test_flag_dirty_attribute_which_does_not_exit() public function test_gh245_dirty_attribute_should_not_raise_php_notice_if_not_dirty() { - $event = new Event(array('title' => "Fun")); + $event = new Event(['title' => "Fun"]); $this->assert_false($event->attribute_is_dirty('description')); $this->assert_true($event->attribute_is_dirty('title')); } @@ -567,7 +567,7 @@ public function test_get_real_attribute_name() public function test_id_setter_works_with_table_without_pk_named_attribute() { - $author = new Author(array('id' => 123)); + $author = new Author(['id' => 123]); $this->assert_equals(123, $author->author_id); } @@ -576,7 +576,7 @@ public function test_query() $row = Author::query('SELECT COUNT(*) AS n FROM authors', null)->fetch(); $this->assert_true($row['n'] > 1); - $row = Author::query('SELECT COUNT(*) AS n FROM authors WHERE name=?', array('Tito'))->fetch(); - $this->assert_equals(array('n' => 1), $row); + $row = Author::query('SELECT COUNT(*) AS n FROM authors WHERE name=?', ['Tito'])->fetch(); + $this->assert_equals(['n' => 1], $row); } } diff --git a/test/ActiveRecordWriteTest.php b/test/ActiveRecordWriteTest.php index 95cfe2128..ecbbb29b7 100644 --- a/test/ActiveRecordWriteTest.php +++ b/test/ActiveRecordWriteTest.php @@ -13,8 +13,8 @@ class DirtyAuthor extends Model { - static $table = 'authors'; - static $before_save = 'before_save'; + public static $table = 'authors'; + public static $before_save = 'before_save'; public function before_save() { @@ -24,13 +24,13 @@ public function before_save() class AuthorWithoutSequence extends Model { - static $table = 'authors'; - static $sequence = 'invalid_seq'; + public static $table = 'authors'; + public static $sequence = 'invalid_seq'; } class AuthorExplicitSequence extends Model { - static $sequence = 'blah_seq'; + public static $sequence = 'blah_seq'; } class ActiveRecordWriteTest extends DatabaseTest @@ -50,14 +50,14 @@ private function make_new_book_and($save = true) public function test_save() { - $venue = new Venue(array('name' => 'Tito')); + $venue = new Venue(['name' => 'Tito']); $venue->save(); $this->expectNotToPerformAssertions(); } public function test_insert() { - $author = new Author(array('name' => 'Blah Blah')); + $author = new Author(['name' => 'Blah Blah']); $author->save(); $this->assert_not_null(Author::find($author->id)); } @@ -70,19 +70,19 @@ public function test_insert_with_no_sequence_defined() throw new DatabaseException(''); } - AuthorWithoutSequence::create(array('name' => 'Bob!')); + AuthorWithoutSequence::create(['name' => 'Bob!']); } public function test_insert_should_quote_keys() { - $author = new Author(array('name' => 'Blah Blah')); + $author = new Author(['name' => 'Blah Blah']); $author->save(); $this->assert_true(strpos($author->connection()->last_query, $author->connection()->quote_name('updated_at')) !== false); } public function test_save_auto_increment_id() { - $venue = new Venue(array('name' => 'Bob')); + $venue = new Venue(['name' => 'Bob']); $venue->save(); $this->assert_true($venue->id > 0); } @@ -148,7 +148,7 @@ public function test_update_attributes() { $book = Book::find(1); $new_name = 'How to lose friends and alienate people'; // jax i'm worried about you - $attrs = array('name' => $new_name); + $attrs = ['name' => $new_name]; $book->update_attributes($attrs); $this->assert_same($new_name, $book->name); @@ -160,7 +160,7 @@ public function test_update_attributes_undefined_property() $this->expectException(UndefinedPropertyException::class); $book = Book::find(1); - $book->update_attributes(array('name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'blah')); + $book->update_attributes(['name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'blah']); } public function test_update_attribute() @@ -200,7 +200,7 @@ public function test_save_blank_value() public function test_dirty_attributes() { $book = $this->make_new_book_and(false); - $this->assert_equals(array('name','special'), array_keys($book->dirty_attributes())); + $this->assert_equals(['name','special'], array_keys($book->dirty_attributes())); } public function test_dirty_attributes_cleared_after_saving() @@ -245,8 +245,8 @@ public function test_dirty_attributes_after_reloading() public function test_dirty_attributes_with_mass_assignment() { $book = Book::first(); - $book->set_attributes(array('name' => 'rivers cuomo')); - $this->assert_equals(array('name'), array_keys($book->dirty_attributes())); + $book->set_attributes(['name' => 'rivers cuomo']); + $this->assert_equals(['name'], array_keys($book->dirty_attributes())); } public function test_timestamps_set_before_save() @@ -277,13 +277,13 @@ public function test_timestamps_updated_at_only_set_before_update() public function test_create() { - $author = Author::create(array('name' => 'Blah Blah')); + $author = Author::create(['name' => 'Blah Blah']); $this->assert_not_null(Author::find($author->id)); } public function test_create_should_set_created_at() { - $author = Author::create(array('name' => 'Blah Blah')); + $author = Author::create(['name' => 'Blah Blah']); $this->assert_not_null($author->created_at); } @@ -291,7 +291,7 @@ public function test_update_with_no_primary_key_defined() { $this->expectException(ActiveRecordException::class); - Author::table()->pk = array(); + Author::table()->pk = []; $author = Author::first(); $author->name = 'blahhhhhhhhhh'; $author->save(); @@ -301,14 +301,14 @@ public function test_delete_with_no_primary_key_defined() { $this->expectException(ActiveRecordException::class); - Author::table()->pk = array(); + Author::table()->pk = []; $author = author::first(); $author->delete(); } public function test_inserting_with_explicit_pk() { - $author = Author::create(array('author_id' => 9999, 'name' => 'blah')); + $author = Author::create(['author_id' => 9999, 'name' => 'blah']); $this->assert_equals(9999, $author->author_id); } @@ -316,7 +316,7 @@ public function test_readonly() { $this->expectException(ReadOnlyException::class); - $author = Author::first(array('readonly' => true)); + $author = Author::first(['readonly' => true]); $author->save(); } @@ -339,7 +339,7 @@ public function test_is_dirty() public function test_set_date_flags_dirty() { - $author = Author::create(array('some_date' => new DateTime())); + $author = Author::create(['some_date' => new DateTime()]); $author = Author::find($author->id); $author->some_date->setDate(2010, 1, 1); $this->assert_has_keys('some_date', $author->dirty_attributes()); @@ -347,7 +347,7 @@ public function test_set_date_flags_dirty() public function test_set_date_flags_dirty_with_php_datetime() { - $author = Author::create(array('some_date' => new \DateTime())); + $author = Author::create(['some_date' => new \DateTime()]); $author = Author::find($author->id); $author->some_date->setDate(2010, 1, 1); $this->assert_has_keys('some_date', $author->dirty_attributes()); @@ -355,19 +355,19 @@ public function test_set_date_flags_dirty_with_php_datetime() public function test_delete_all_with_conditions_as_string() { - $num_affected = Author::delete_all(array('conditions' => 'parent_author_id = 2')); + $num_affected = Author::delete_all(['conditions' => 'parent_author_id = 2']); $this->assert_equals(2, $num_affected); } public function test_delete_all_with_conditions_as_hash() { - $num_affected = Author::delete_all(array('conditions' => array('parent_author_id' => 2))); + $num_affected = Author::delete_all(['conditions' => ['parent_author_id' => 2]]); $this->assert_equals(2, $num_affected); } public function test_delete_all_with_conditions_as_array() { - $num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2))); + $num_affected = Author::delete_all(['conditions' => ['parent_author_id = ?', 2]]); $this->assert_equals(2, $num_affected); } @@ -377,21 +377,21 @@ public function test_delete_all_with_limit_and_order() $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause'); } - $num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2), 'limit' => 1, 'order' => 'name asc')); + $num_affected = Author::delete_all(['conditions' => ['parent_author_id = ?', 2], 'limit' => 1, 'order' => 'name asc']); $this->assert_equals(1, $num_affected); $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false); } public function test_update_all_with_set_as_string() { - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2')); + $num_affected = Author::update_all(['set' => 'parent_author_id = 2']); $this->assert_equals($this->expected_update_all_count(), $num_affected); $this->assert_equals(4, Author::count_by_parent_author_id(2)); } public function test_update_all_with_set_as_hash() { - $num_affected = Author::update_all(array('set' => array('parent_author_id' => 2))); + $num_affected = Author::update_all(['set' => ['parent_author_id' => 2]]); $this->assert_equals($this->expected_update_all_count(), $num_affected); } @@ -408,26 +408,26 @@ private function expected_update_all_count() * TODO: not implemented * public function test_update_all_with_set_as_array() * { - * $num_affected = Author::update_all(array('set' => array('parent_author_id = ?', 2))); + * $num_affected = Author::update_all(['set' => ['parent_author_id = ?', 2]]); * $this->assert_equals(2, $num_affected); * } */ public function test_update_all_with_conditions_as_string() { - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => "name = 'Tito'")); + $num_affected = Author::update_all(['set' => 'parent_author_id = 2', 'conditions' => "name = 'Tito'"]); $this->assert_equals(1, $num_affected); } public function test_update_all_with_conditions_as_hash() { - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name' => "Tito"))); + $num_affected = Author::update_all(['set' => 'parent_author_id = 2', 'conditions' => ['name' => "Tito"]]); $this->assert_equals(1, $num_affected); } public function test_update_all_with_conditions_as_array() { - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name = ?', "Tito"))); + $num_affected = Author::update_all(['set' => 'parent_author_id = 2', 'conditions' => ['name = ?', "Tito"]]); $this->assert_equals(1, $num_affected); } @@ -437,14 +437,14 @@ public function test_update_all_with_limit_and_order() $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause'); } - $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'limit' => 1, 'order' => 'name asc')); + $num_affected = Author::update_all(['set' => 'parent_author_id = 2', 'limit' => 1, 'order' => 'name asc']); $this->assert_equals(1, $num_affected); $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false); } public function test_update_native_datetime() { - $author = Author::create(array('name' => 'Blah Blah')); + $author = Author::create(['name' => 'Blah Blah']); $native_datetime = new \DateTime('1983-12-05'); $author->some_date = $native_datetime; $this->assert_false($native_datetime === $author->some_date); @@ -452,7 +452,7 @@ public function test_update_native_datetime() public function test_update_our_datetime() { - $author = Author::create(array('name' => 'Blah Blah')); + $author = Author::create(['name' => 'Blah Blah']); $our_datetime = new DateTime('1983-12-05'); $author->some_date = $our_datetime; $this->assert_true($our_datetime === $author->some_date); diff --git a/test/CacheModelTest.php b/test/CacheModelTest.php index 07764f025..818cd5102 100644 --- a/test/CacheModelTest.php +++ b/test/CacheModelTest.php @@ -54,14 +54,14 @@ public function test_cache_key() $method = $this->set_method_public(Author::class, 'cache_key'); $author = Author::first(); - $this->assert_equals(Author::class . "-1", $method->invokeArgs($author, array())); + $this->assert_equals(Author::class . "-1", $method->invokeArgs($author, [])); } public function test_model_cache_find_by_pk() { $publisher = Publisher::find(1); $method = $this->set_method_public(Publisher::class, 'cache_key'); - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); $from_cache = Cache::$adapter->read($cache_key); $this->assertEquals($publisher->name, $from_cache->name); @@ -69,13 +69,13 @@ public function test_model_cache_find_by_pk() public function test_model_cache_new() { - $publisher = new Publisher(array( + $publisher = new Publisher([ 'name' => 'HarperCollins' - )); + ]); $publisher->save(); $method = $this->set_method_public(Publisher::class, 'cache_key'); - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); // Model is cached on first find $actual = Publisher::find($publisher->id); @@ -90,7 +90,7 @@ public function test_model_cache_find() $publishers = Publisher::all(); foreach ($publishers as $publisher) { - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); $from_cache = Cache::$adapter->read($cache_key); $this->assertEquals($publisher->name, $from_cache->name); @@ -101,7 +101,7 @@ public function test_regular_models_not_cached() { $method = $this->set_method_public(Author::class, 'cache_key'); $author = Author::first(); - $cache_key = $method->invokeArgs($author, array()); + $cache_key = $method->invokeArgs($author, []); $this->assertFalse(Cache::$adapter->read($cache_key)); } @@ -109,7 +109,7 @@ public function test_model_delete_from_cache() { $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); $publisher->delete(); @@ -122,7 +122,7 @@ public function test_model_update_cache() $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); $this->assertEquals('Random House', $publisher->name); $from_cache = Cache::$adapter->read($cache_key); @@ -143,11 +143,11 @@ public function test_model_reload_expires_cache() $method = $this->set_method_public(Publisher::class, 'cache_key'); $publisher = Publisher::find(1); - $cache_key = $method->invokeArgs($publisher, array()); + $cache_key = $method->invokeArgs($publisher, []); $this->assertEquals('Random House', $publisher->name); // Raw query to not update model properties - Publisher::query('UPDATE publishers SET name = ? WHERE publisher_id = ?', array('Specific House', 1)); + Publisher::query('UPDATE publishers SET name = ? WHERE publisher_id = ?', ['Specific House', 1]); $publisher->reload(); diff --git a/test/ColumnTest.php b/test/ColumnTest.php index d9cdd5c4d..fe869a0fd 100644 --- a/test/ColumnTest.php +++ b/test/ColumnTest.php @@ -106,12 +106,12 @@ public function test_cast() public function test_cast_leave_null_alone() { - $types = array( + $types = [ Column::STRING, Column::INTEGER, Column::DECIMAL, Column::DATETIME, - Column::DATE); + Column::DATE]; foreach ($types as $type) { $this->assert_cast($type, null, null); diff --git a/test/ConfigTest.php b/test/ConfigTest.php index 2e3dc2b30..038f2f4fd 100644 --- a/test/ConfigTest.php +++ b/test/ConfigTest.php @@ -36,7 +36,7 @@ class ConfigTest extends SnakeCase_PHPUnit_Framework_TestCase public function setUp(): void { $this->config = new Config(); - $this->connections = array('development' => 'mysql://blah/development', 'test' => 'mysql://blah/test'); + $this->connections = ['development' => 'mysql://blah/development', 'test' => 'mysql://blah/test']; $this->config->set_connections($this->connections); } @@ -82,7 +82,7 @@ public function test_get_default_connection_string_when_connection_name_is_not_v public function test_default_connection_is_set_when_only_one_connection_is_present() { - $this->config->set_connections(array('development' => $this->connections['development'])); + $this->config->set_connections(['development' => $this->connections['development']]); $this->assert_equals('development', $this->config->get_default_connection()); } diff --git a/test/DateTimeTest.php b/test/DateTimeTest.php index 31afa8ec8..0c19a00ce 100644 --- a/test/DateTimeTest.php +++ b/test/DateTimeTest.php @@ -41,7 +41,7 @@ private function assert_dirtifies($method /*, method params, ...*/) $args = func_get_args(); array_shift($args); - call_user_func_array(array($datetime,$method), $args); + call_user_func_array([$datetime,$method], $args); $this->assert_has_keys('some_date', $model->dirty_attributes()); } diff --git a/test/ExpressionsTest.php b/test/ExpressionsTest.php index fbc46480b..c9cc6419c 100644 --- a/test/ExpressionsTest.php +++ b/test/ExpressionsTest.php @@ -11,27 +11,27 @@ class ExpressionsTest extends SnakeCase_PHPUnit_Framework_TestCase public function test_values() { $c = new Expressions(null, 'a=? and b=?', 1, 2); - $this->assert_equals(array(1,2), $c->values()); + $this->assert_equals([1,2], $c->values()); } public function test_one_variable() { $c = new Expressions(null, 'name=?', 'Tito'); $this->assert_equals('name=?', $c->to_s()); - $this->assert_equals(array('Tito'), $c->values()); + $this->assert_equals(['Tito'], $c->values()); } public function test_array_variable() { - $c = new Expressions(null, 'name IN(?) and id=?', array('Tito','George'), 1); - $this->assert_equals(array(array('Tito','George'),1), $c->values()); + $c = new Expressions(null, 'name IN(?) and id=?', ['Tito','George'], 1); + $this->assert_equals([['Tito','George'],1], $c->values()); } public function test_multiple_variables() { $c = new Expressions(null, 'name=? and book=?', 'Tito', 'Sharks'); $this->assert_equals('name=? and book=?', $c->to_s()); - $this->assert_equals(array('Tito','Sharks'), $c->values()); + $this->assert_equals(['Tito','Sharks'], $c->values()); } public function test_to_string() @@ -42,7 +42,7 @@ public function test_to_string() public function test_to_string_with_array_variable() { - $c = new Expressions(null, 'name IN(?) and id=?', array('Tito','George'), 1); + $c = new Expressions(null, 'name IN(?) and id=?', ['Tito','George'], 1); $this->assert_equals('name IN(?,?) and id=?', $c->to_s()); } @@ -72,38 +72,38 @@ public function test_null_variable() { $a = new Expressions(null, 'name=?', null); $this->assert_equals('name=?', $a->to_s()); - $this->assert_equals(array(null), $a->values()); + $this->assert_equals([null], $a->values()); } public function test_zero_variable() { $a = new Expressions(null, 'name=?', 0); $this->assert_equals('name=?', $a->to_s()); - $this->assert_equals(array(0), $a->values()); + $this->assert_equals([0], $a->values()); } public function test_empty_array_variable() { - $a = new Expressions(null, 'id IN(?)', array()); + $a = new Expressions(null, 'id IN(?)', []); $this->assert_equals('id IN(?)', $a->to_s()); - $this->assert_equals(array(array()), $a->values()); + $this->assert_equals([[]], $a->values()); } public function test_ignore_invalid_parameter_marker() { - $a = new Expressions(null, "question='Do you love backslashes?' and id in(?)", array(1,2)); + $a = new Expressions(null, "question='Do you love backslashes?' and id in(?)", [1,2]); $this->assert_equals("question='Do you love backslashes?' and id in(?,?)", $a->to_s()); } public function test_ignore_parameter_marker_with_escaped_quote() { - $a = new Expressions(null, "question='Do you love''s backslashes?' and id in(?)", array(1,2)); + $a = new Expressions(null, "question='Do you love''s backslashes?' and id in(?)", [1,2]); $this->assert_equals("question='Do you love''s backslashes?' and id in(?,?)", $a->to_s()); } public function test_ignore_parameter_marker_with_backspace_escaped_quote() { - $a = new Expressions(null, "question='Do you love\\'s backslashes?' and id in(?)", array(1,2)); + $a = new Expressions(null, "question='Do you love\\'s backslashes?' and id in(?)", [1,2]); $this->assert_equals("question='Do you love\\'s backslashes?' and id in(?,?)", $a->to_s()); } @@ -115,7 +115,7 @@ public function test_substitute() public function test_substitute_quotes_scalars_but_not_others() { - $a = new Expressions(null, 'id in(?)', array(1,'2',3.5)); + $a = new Expressions(null, 'id in(?)', [1,'2',3.5]); $this->assert_equals("id in(1,'2',3.5)", $a->to_s(true)); } @@ -127,13 +127,13 @@ public function test_substitute_where_value_has_question_mark() public function test_substitute_array_value() { - $a = new Expressions(null, 'id in(?)', array(1,2)); + $a = new Expressions(null, 'id in(?)', [1,2]); $this->assert_equals("id in(1,2)", $a->to_s(true)); } public function test_substitute_escapes_quotes() { - $a = new Expressions(null, 'name=? or name in(?)', "Tito's Guild", array(1,"Tito's Guild")); + $a = new Expressions(null, 'name=? or name in(?)', "Tito's Guild", [1,"Tito's Guild"]); $this->assert_equals("name='Tito''s Guild' or name in(1,'Tito''s Guild')", $a->to_s(true)); } @@ -155,14 +155,14 @@ public function test_bind() { $a = new Expressions(null, 'name=? and id=?', 'Tito'); $a->bind(2, 1); - $this->assert_equals(array('Tito',1), $a->values()); + $this->assert_equals(['Tito',1], $a->values()); } public function test_bind_overwrite_existing() { $a = new Expressions(null, 'name=? and id=?', 'Tito', 1); $a->bind(2, 99); - $this->assert_equals(array('Tito',99), $a->values()); + $this->assert_equals(['Tito',99], $a->values()); } public function test_bind_invalid_parameter_number() @@ -177,7 +177,7 @@ public function test_subsitute_using_alternate_values() { $a = new Expressions(null, 'name=?', 'Tito'); $this->assert_equals("name='Tito'", $a->to_s(true)); - $x = array('values' => array('Hocus')); + $x = ['values' => ['Hocus']]; $this->assert_equals("name='Hocus'", $a->to_s(true, $x)); } @@ -189,19 +189,19 @@ public function test_null_value() public function test_hash_with_default_glue() { - $a = new Expressions(null, array('id' => 1, 'name' => 'Tito')); + $a = new Expressions(null, ['id' => 1, 'name' => 'Tito']); $this->assert_equals('id=? AND name=?', $a->to_s()); } public function test_hash_with_glue() { - $a = new Expressions(null, array('id' => 1, 'name' => 'Tito'), ', '); + $a = new Expressions(null, ['id' => 1, 'name' => 'Tito'], ', '); $this->assert_equals('id=?, name=?', $a->to_s()); } public function test_hash_with_array() { - $a = new Expressions(null, array('id' => 1, 'name' => array('Tito','Mexican'))); + $a = new Expressions(null, ['id' => 1, 'name' => ['Tito','Mexican']]); $this->assert_equals('id=? AND name IN(?,?)', $a->to_s()); } } diff --git a/test/HasManyThroughTest.php b/test/HasManyThroughTest.php index 771080b8c..af7c936c1 100644 --- a/test/HasManyThroughTest.php +++ b/test/HasManyThroughTest.php @@ -26,11 +26,11 @@ public function test_gh101_has_many_through() public function test_gh101_has_many_through_include() { - $user = User::find(1, array( - 'include' => array( + $user = User::find(1, [ + 'include' => [ 'user_newsletters' - ) - )); + ] + ]); $this->assert_equals(1, $user->id); $this->assert_equals(1, $user->user_newsletters[0]->id); @@ -38,20 +38,20 @@ public function test_gh101_has_many_through_include() public function test_gh107_has_many_through_include_eager() { - $venue = Venue::find(1, array('include' => array('events'))); + $venue = Venue::find(1, ['include' => ['events']]); $this->assert_equals(1, $venue->events[0]->id); - $venue = Venue::find(1, array('include' => array('hosts'))); + $venue = Venue::find(1, ['include' => ['hosts']]); $this->assert_equals(1, $venue->hosts[0]->id); } public function test_gh107_has_many_though_include_eager_with_namespace() { - $user = User::find(1, array( - 'include' => array( + $user = User::find(1, [ + 'include' => [ 'newsletters' - ) - )); + ] + ]); $this->assert_equals(1, $user->id); $this->assert_equals(1, $user->newsletters[0]->id); diff --git a/test/ModelCallbackTest.php b/test/ModelCallbackTest.php index 20356ccab..f5024388b 100644 --- a/test/ModelCallbackTest.php +++ b/test/ModelCallbackTest.php @@ -19,10 +19,10 @@ public function setUp(): void public function register_and_invoke_callbacks($callbacks, $return, $closure) { if (!is_array($callbacks)) { - $callbacks = array($callbacks); + $callbacks = [$callbacks]; } - $fired = array(); + $fired = []; foreach ($callbacks as $name) { $this->callback->register($name, function ($model) use (&$fired, $name, $return) { @@ -50,7 +50,7 @@ public function assert_does_not_fire($callbacks, $closure) public function assert_fires_returns_false($callbacks, $only_fire, $closure) { if (!is_array($only_fire)) { - $only_fire = array($only_fire); + $only_fire = [$only_fire]; } $executed = $this->register_and_invoke_callbacks($callbacks, false, $closure); @@ -62,7 +62,7 @@ public function assert_fires_returns_false($callbacks, $only_fire, $closure) public function test_after_construct_fires_by_default() { - $this->assert_fires(array('after_construct'), function ($model) { + $this->assert_fires(['after_construct'], function ($model) { new Venue(); }); } @@ -70,7 +70,7 @@ public function test_after_construct_fires_by_default() public function test_fire_validation_callbacks_on_insert() { $this->assert_fires( - array('before_validation','after_validation','before_validation_on_create','after_validation_on_create'), + ['before_validation','after_validation','before_validation_on_create','after_validation_on_create'], function ($model) { $model = new Venue(); $model->save(); @@ -81,7 +81,7 @@ function ($model) { public function test_fire_validation_callbacks_on_update() { $this->assert_fires( - array('before_validation','after_validation','before_validation_on_update','after_validation_on_update'), + ['before_validation','after_validation','before_validation_on_update','after_validation_on_update'], function ($model) { $model = Venue::first(); $model->save(); @@ -99,7 +99,7 @@ public function test_validation_call_backs_not_fired_due_to_bypassing_validation public function test_before_validation_returning_false_cancels_callbacks() { $this->assert_fires_returns_false( - array('before_validation','after_validation'), + ['before_validation','after_validation'], 'before_validation', function ($model) { $model->save(); @@ -110,7 +110,7 @@ function ($model) { public function test_fires_before_save_and_before_update_when_updating() { $this->assert_fires( - array('before_save','before_update'), + ['before_save','before_update'], function ($model) { $model = Venue::first(); $model->name = "something new"; @@ -122,7 +122,7 @@ function ($model) { public function test_before_save_returning_false_cancels_callbacks() { $this->assert_fires_returns_false( - array('before_save','before_create'), + ['before_save','before_create'], 'before_save', function ($model) { $model = new Venue(); @@ -134,7 +134,7 @@ function ($model) { public function test_destroy() { $this->assert_fires( - array('before_destroy','after_destroy'), + ['before_destroy','after_destroy'], function ($model) { $model->delete(); } diff --git a/test/PgsqlAdapterTest.php b/test/PgsqlAdapterTest.php index 9596633d9..bc5f2c0d5 100644 --- a/test/PgsqlAdapterTest.php +++ b/test/PgsqlAdapterTest.php @@ -21,7 +21,7 @@ public function test_insert_id() public function test_insert_id_with_params() { - $x = array('name'); + $x = ['name']; $this->conn->query("INSERT INTO authors(author_id,name) VALUES(nextval('authors_author_id_seq'),?)", $x); $this->assert_true($this->conn->insert_id('authors_author_id_seq') > 0); } diff --git a/test/RelationshipTest.php b/test/RelationshipTest.php index ef9f3de20..ab3a512fc 100644 --- a/test/RelationshipTest.php +++ b/test/RelationshipTest.php @@ -584,7 +584,7 @@ public function test_eager_loading_has_many_x() // public function test_eager_loading_has_many_x_with_caching() // { - // Publisher::find(array(1, 2, 3), array('include' => 'authors')); + // Publisher::find([1, 2, 3], ['include' => 'authors']); // $this->assert_sql_has("WHERE publisher_id IN(?)", ActiveRecord\Table::load('Author')->last_sql); // } diff --git a/test/SQLBuilderTest.php b/test/SQLBuilderTest.php index 6ea28cca7..11768ca69 100644 --- a/test/SQLBuilderTest.php +++ b/test/SQLBuilderTest.php @@ -35,7 +35,7 @@ public function assert_conditions($expected_sql, $values, $underscored_string, $ return $s !== null; })), array_values($cond)); } else { - $this->assert_equals(array(), $cond); + $this->assert_equals([], $cond); } } @@ -52,31 +52,31 @@ public function test_nothing() public function test_where_with_array() { - $this->sql->where("id=? AND name IN(?)", 1, array('Tito','Mexican')); + $this->sql->where("id=? AND name IN(?)", 1, ['Tito','Mexican']); $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name IN(?,?)", (string)$this->sql); - $this->assert_equals(array(1,'Tito','Mexican'), $this->sql->get_where_values()); + $this->assert_equals([1,'Tito','Mexican'], $this->sql->get_where_values()); } public function test_where_with_hash() { - $this->sql->where(array('id' => 1, 'name' => 'Tito')); + $this->sql->where(['id' => 1, 'name' => 'Tito']); $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name=?", (string)$this->sql); - $this->assert_equals(array(1,'Tito'), $this->sql->get_where_values()); + $this->assert_equals([1,'Tito'], $this->sql->get_where_values()); } public function test_where_with_hash_and_array() { - $this->sql->where(array('id' => 1, 'name' => array('Tito','Mexican'))); + $this->sql->where(['id' => 1, 'name' => ['Tito','Mexican']]); $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name IN(?,?)", (string)$this->sql); - $this->assert_equals(array(1,'Tito','Mexican'), $this->sql->get_where_values()); + $this->assert_equals([1,'Tito','Mexican'], $this->sql->get_where_values()); } public function test_gh134_where_with_hash_and_null() { - $this->sql->where(array('id' => 1, 'name' => null)); + $this->sql->where(['id' => 1, 'name' => null]); $this->assert_sql_has("SELECT * FROM authors WHERE id=? AND name IS NULL", (string)$this->sql); // the null is rendered as a literal IS NULL, not bound - $this->assert_equals(array(1), $this->sql->get_where_values()); + $this->assert_equals([1], $this->sql->get_where_values()); } public function test_where_with_null() @@ -134,33 +134,33 @@ public function test_all_clauses_after_where_should_be_correctly_ordered() $this->sql->having("created_at > '2009-01-01'"); $this->sql->order('name'); $this->sql->group('name'); - $this->sql->where(array('id' => 1)); + $this->sql->where(['id' => 1]); $this->assert_sql_has($this->conn->limit("SELECT * FROM authors WHERE id=? GROUP BY name HAVING created_at > '2009-01-01' ORDER BY name", 1, 10), (string)$this->sql); } public function test_insert_requires_hash() { $this->expectException(ActiveRecordException::class); - $this->sql->insert(array(1)); + $this->sql->insert([1]); } public function test_insert() { - $this->sql->insert(array('id' => 1, 'name' => 'Tito')); + $this->sql->insert(['id' => 1, 'name' => 'Tito']); $this->assert_sql_has("INSERT INTO authors(id,name) VALUES(?,?)", (string)$this->sql); } public function test_insert_with_null() { - $this->sql->insert(array('id' => 1, 'name' => null)); + $this->sql->insert(['id' => 1, 'name' => null]); $this->assert_sql_has("INSERT INTO authors(id,name) VALUES(?,?)", $this->sql->to_s()); } public function test_update_with_hash() { - $this->sql->update(array('id' => 1, 'name' => 'Tito'))->where('id=1 AND name IN(?)', array('Tito','Mexican')); + $this->sql->update(['id' => 1, 'name' => 'Tito'])->where('id=1 AND name IN(?)', ['Tito','Mexican']); $this->assert_sql_has("UPDATE authors SET id=?, name=? WHERE id=1 AND name IN(?,?)", (string)$this->sql); - $this->assert_equals(array(1,'Tito','Tito','Mexican'), $this->sql->bind_values()); + $this->assert_equals([1,'Tito','Tito','Mexican'], $this->sql->bind_values()); } public function test_update_with_limit_and_order() @@ -169,7 +169,7 @@ public function test_update_with_limit_and_order() $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE operation'); } - $this->sql->update(array('id' => 1))->order('name asc')->limit(1); + $this->sql->update(['id' => 1])->order('name asc')->limit(1); $this->assert_sql_has("UPDATE authors SET id=? ORDER BY name asc LIMIT 1", $this->sql->to_s()); } @@ -181,7 +181,7 @@ public function test_update_with_string() public function test_update_with_null() { - $this->sql->update(array('id' => 1, 'name' => null))->where('id=1'); + $this->sql->update(['id' => 1, 'name' => null])->where('id=1'); $this->assert_sql_has("UPDATE authors SET id=?, name=? WHERE id=1", $this->sql->to_s()); } @@ -193,16 +193,16 @@ public function test_delete() public function test_delete_with_where() { - $this->sql->delete('id=? or name in(?)', 1, array('Tito','Mexican')); + $this->sql->delete('id=? or name in(?)', 1, ['Tito','Mexican']); $this->assert_equals('DELETE FROM authors WHERE id=? or name in(?,?)', $this->sql->to_s()); - $this->assert_equals(array(1,'Tito','Mexican'), $this->sql->bind_values()); + $this->assert_equals([1,'Tito','Mexican'], $this->sql->bind_values()); } public function test_delete_with_hash() { - $this->sql->delete(array('id' => 1, 'name' => array('Tito','Mexican'))); + $this->sql->delete(['id' => 1, 'name' => ['Tito','Mexican']]); $this->assert_sql_has("DELETE FROM authors WHERE id=? AND name IN(?,?)", $this->sql->to_s()); - $this->assert_equals(array(1,'Tito','Mexican'), $this->sql->get_where_values()); + $this->assert_equals([1,'Tito','Mexican'], $this->sql->get_where_values()); } public function test_delete_with_limit_and_order() @@ -211,7 +211,7 @@ public function test_delete_with_limit_and_order() $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with DELETE operation'); } - $this->sql->delete(array('id' => 1))->order('name asc')->limit(1); + $this->sql->delete(['id' => 1])->order('name asc')->limit(1); $this->assert_sql_has("DELETE FROM authors WHERE id=? ORDER BY name asc LIMIT 1", $this->sql->to_s()); } @@ -228,25 +228,25 @@ public function test_reverse_order() public function test_create_conditions_from_underscored_string() { - $this->assert_conditions('id=? AND name=? OR z=?', array(1,'Tito','X'), 'id_and_name_or_z'); - $this->assert_conditions('id=?', array(1), 'id'); - $this->assert_conditions('id IN(?)', array(array(1,2)), 'id'); + $this->assert_conditions('id=? AND name=? OR z=?', [1,'Tito','X'], 'id_and_name_or_z'); + $this->assert_conditions('id=?', [1], 'id'); + $this->assert_conditions('id IN(?)', [[1,2]], 'id'); } public function test_create_conditions_from_underscored_string_with_nulls() { - $this->assert_conditions('id=? AND name IS NULL', array(1,null), 'id_and_name'); + $this->assert_conditions('id=? AND name IS NULL', [1,null], 'id_and_name'); } public function test_create_conditions_from_underscored_string_with_missing_args() { - $this->assert_conditions('id=? AND name IS NULL OR z IS NULL', array(1,null), 'id_and_name_or_z'); + $this->assert_conditions('id=? AND name IS NULL OR z IS NULL', [1,null], 'id_and_name_or_z'); $this->assert_conditions('id IS NULL', null, 'id'); } public function test_create_conditions_from_underscored_string_with_blank() { - $this->assert_conditions('id=? AND name IS NULL OR z=?', array(1,null,''), 'id_and_name_or_z'); + $this->assert_conditions('id=? AND name IS NULL OR z=?', [1,null,''], 'id_and_name_or_z'); } public function test_create_conditions_from_underscored_string_invalid() @@ -257,22 +257,22 @@ public function test_create_conditions_from_underscored_string_invalid() public function test_create_conditions_from_underscored_string_with_mapped_columns() { - $this->assert_conditions('id=? AND name=?', array(1,'Tito'), 'id_and_my_name', array('my_name' => 'name')); + $this->assert_conditions('id=? AND name=?', [1,'Tito'], 'id_and_my_name', ['my_name' => 'name']); } public function test_create_hash_from_underscored_string() { - $values = array(1,'Tito'); + $values = [1,'Tito']; $hash = SQLBuilder::create_hash_from_underscored_string('id_and_my_name', $values); - $this->assert_equals(array('id' => 1, 'my_name' => 'Tito'), $hash); + $this->assert_equals(['id' => 1, 'my_name' => 'Tito'], $hash); } public function test_create_hash_from_underscored_string_with_mapped_columns() { - $values = array(1,'Tito'); - $map = array('my_name' => 'name'); + $values = [1,'Tito']; + $map = ['my_name' => 'name']; $hash = SQLBuilder::create_hash_from_underscored_string('id_and_my_name', $values, $map); - $this->assert_equals(array('id' => 1, 'name' => 'Tito'), $hash); + $this->assert_equals(['id' => 1, 'name' => 'Tito'], $hash); } public function test_where_with_joins_prepends_table_name_to_fields() @@ -280,7 +280,7 @@ public function test_where_with_joins_prepends_table_name_to_fields() $joins = 'INNER JOIN books ON (books.id = authors.id)'; // joins needs to be called prior to where $this->sql->joins($joins); - $this->sql->where(array('id' => 1, 'name' => 'Tito')); + $this->sql->where(['id' => 1, 'name' => 'Tito']); $this->assert_sql_has("SELECT * FROM authors $joins WHERE authors.id=? AND authors.name=?", (string)$this->sql); } diff --git a/test/SqliteAdapterTest.php b/test/SqliteAdapterTest.php index 8228da951..c74539198 100644 --- a/test/SqliteAdapterTest.php +++ b/test/SqliteAdapterTest.php @@ -36,7 +36,7 @@ public function testConnectToInvalidDatabaseShouldNotCreateDbFile() public function test_limit_with_null_offset_does_not_contain_offset() { - $ret = array(); + $ret = []; $sql = 'SELECT * FROM authors ORDER BY name ASC'; $this->conn->query_and_fetch($this->conn->limit($sql, null, 1), function ($row) use (&$ret) { $ret[] = $row; diff --git a/test/ValidatesFormatOfTest.php b/test/ValidatesFormatOfTest.php index 247981b57..181b276db 100644 --- a/test/ValidatesFormatOfTest.php +++ b/test/ValidatesFormatOfTest.php @@ -6,8 +6,8 @@ class BookFormat extends Model { - static $table = 'books'; - static $validates_format_of = [ + public static $table = 'books'; + public static $validates_format_of = [ ['name'] ]; } diff --git a/test/ValidatesInclusionAndExclusionOfTest.php b/test/ValidatesInclusionAndExclusionOfTest.php index 7d755e79e..48c13c9d6 100644 --- a/test/ValidatesInclusionAndExclusionOfTest.php +++ b/test/ValidatesInclusionAndExclusionOfTest.php @@ -5,7 +5,7 @@ class BookExclusion extends Model { - static $table = 'books'; + public static $table = 'books'; public static $validates_exclusion_of = [ ['name', 'in' => ['blah', 'alpha', 'bravo']] ]; @@ -13,7 +13,7 @@ class BookExclusion extends Model class BookInclusion extends Model { - static $table = 'books'; + public static $table = 'books'; public static $validates_inclusion_of = [ ['name', 'in' => ['blah', 'tanker', 'shark']] diff --git a/test/ValidatesLengthOfTest.php b/test/ValidatesLengthOfTest.php index ba43f1ecd..7b6561759 100644 --- a/test/ValidatesLengthOfTest.php +++ b/test/ValidatesLengthOfTest.php @@ -6,14 +6,14 @@ class BookLength extends Model { - static $table = 'books'; - static $validates_length_of = []; + public static $table = 'books'; + public static $validates_length_of = []; } class BookSize extends Model { - static $table = 'books'; - static $validates_size_of = []; + public static $table = 'books'; + public static $validates_size_of = []; } class ValidatesLengthOfTest extends DatabaseTest diff --git a/test/ValidatesNumericalityOfTest.php b/test/ValidatesNumericalityOfTest.php index 55dd6e195..bea40f41d 100644 --- a/test/ValidatesNumericalityOfTest.php +++ b/test/ValidatesNumericalityOfTest.php @@ -14,13 +14,13 @@ class BookNumericality extends Model class ValidatesNumericalityOfTest extends DatabaseTest { - static $NULL = [null]; - static $BLANK = ["", " ", " \t \r \n"]; - static $FLOAT_STRINGS = ['0.0','+0.0','-0.0','10.0','10.5','-10.5','-0.0001','-090.1']; - static $INTEGER_STRINGS = ['0', '+0', '-0', '10', '+10', '-10', '0090', '-090']; - static $FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001]; - static $INTEGERS = [0, 10, -10]; - static $JUNK = ["not a number", "42 not a number", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]; + public static $NULL = [null]; + public static $BLANK = ["", " ", " \t \r \n"]; + public static $FLOAT_STRINGS = ['0.0','+0.0','-0.0','10.0','10.5','-10.5','-0.0001','-090.1']; + public static $INTEGER_STRINGS = ['0', '+0', '-0', '10', '+10', '-10', '0090', '-090']; + public static $FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001]; + public static $INTEGERS = [0, 10, -10]; + public static $JUNK = ["not a number", "42 not a number", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]; public function setUp(): void { @@ -65,7 +65,7 @@ private function assert_valid($values, $msg = null) public function test_numericality() { - //$this->assert_invalid(array("0xdeadbeef")); + //$this->assert_invalid(["0xdeadbeef"]); $this->assert_valid(array_merge(self::$FLOATS, self::$INTEGERS)); $this->assert_invalid(array_merge(self::$NULL, self::$BLANK, self::$JUNK)); diff --git a/test/ValidatesPresenceOfTest.php b/test/ValidatesPresenceOfTest.php index 7d891aa8a..1ec2aa3e8 100644 --- a/test/ValidatesPresenceOfTest.php +++ b/test/ValidatesPresenceOfTest.php @@ -5,18 +5,18 @@ class BookPresence extends Model { - static $table_name = 'books'; + public static $table_name = 'books'; - static $validates_presence_of = [ + public static $validates_presence_of = [ ['name'] ]; } class AuthorPresence extends Model { - static $table_name = 'authors'; + public static $table_name = 'authors'; - static $validates_presence_of = [ + public static $validates_presence_of = [ ['some_date'] ]; } diff --git a/test/ValidationsTest.php b/test/ValidationsTest.php index ca2829a5e..61a60caab 100644 --- a/test/ValidationsTest.php +++ b/test/ValidationsTest.php @@ -6,11 +6,11 @@ class BookValidations extends Model { - static $table_name = 'books'; - static $alias_attribute = ['name_alias' => 'name', 'x' => 'secondary_author_id']; - static $validates_presence_of = []; - static $validates_uniqueness_of = []; - static $custom_validator_error_msg = 'failed custom validation'; + public static $table_name = 'books'; + public static $alias_attribute = ['name_alias' => 'name', 'x' => 'secondary_author_id']; + public static $validates_presence_of = []; + public static $validates_uniqueness_of = []; + public static $custom_validator_error_msg = 'failed custom validation'; // fired for every validation - but only used for custom validation test public function validate() @@ -23,8 +23,8 @@ public function validate() class ValuestoreValidations extends Model { - static $table_name = 'valuestore'; - static $validates_uniqueness_of = []; + public static $table_name = 'valuestore'; + public static $validates_uniqueness_of = []; } class ValidationsTest extends DatabaseTest diff --git a/test/helpers/AdapterTest.php b/test/helpers/AdapterTest.php index 64845f426..746b2bb6d 100644 --- a/test/helpers/AdapterTest.php +++ b/test/helpers/AdapterTest.php @@ -226,7 +226,7 @@ public function test_fetch() { $sth = $this->conn->query('SELECT * FROM authors WHERE author_id IN(1,2,3)'); $i = 0; - $ids = array(); + $ids = []; while (($row = $sth->fetch())) { ++$i; @@ -234,12 +234,12 @@ public function test_fetch() } $this->assert_equals(3, $i); - $this->assert_equals(array(1,2,3), $ids); + $this->assert_equals([1,2,3], $ids); } public function test_query_with_params() { - $x = array('Bill Clinton','Tito'); + $x = ['Bill Clinton','Tito']; $sth = $this->conn->query('SELECT * FROM authors WHERE name IN(?,?) ORDER BY name DESC', $x); $row = $sth->fetch(); $this->assert_equals('Tito', $row['name']); @@ -265,7 +265,7 @@ public function test_insert_id() public function test_insert_id_with_params() { - $x = array('name'); + $x = ['name']; $this->conn->query('INSERT INTO authors(name) VALUES(?)', $x); $this->assert_true($this->conn->insert_id() > 0); } @@ -285,7 +285,7 @@ public function test_escape() public function test_columnsx() { $columns = $this->conn->columns('authors'); - $names = array('author_id','parent_author_id','name','updated_at','created_at','some_Date','some_time','some_text','encrypted_password','mixedCaseField'); + $names = ['author_id','parent_author_id','name','updated_at','created_at','some_Date','some_time','some_text','encrypted_password','mixedCaseField']; foreach ($names as $field) { $this->assert_true(array_key_exists($field, $columns)); @@ -314,7 +314,7 @@ public function test_columns_decimal() private function limit($offset, $limit) { - $ret = array(); + $ret = []; $sql = 'SELECT * FROM authors ORDER BY name ASC'; $this->conn->query_and_fetch($this->conn->limit($sql, $offset, $limit), function ($row) use (&$ret) { $ret[] = $row; @@ -324,27 +324,27 @@ private function limit($offset, $limit) public function test_limit() { - $this->assert_equals(array(2,1), $this->limit(1, 2)); + $this->assert_equals([2,1], $this->limit(1, 2)); } public function test_limit_to_first_record() { - $this->assert_equals(array(3), $this->limit(0, 1)); + $this->assert_equals([3], $this->limit(0, 1)); } public function test_limit_to_last_record() { - $this->assert_equals(array(1), $this->limit(2, 1)); + $this->assert_equals([1], $this->limit(2, 1)); } public function test_limit_with_null_offset() { - $this->assert_equals(array(3), $this->limit(null, 1)); + $this->assert_equals([3], $this->limit(null, 1)); } public function test_limit_with_nulls() { - $this->assert_equals(array(), $this->limit(null, null)); + $this->assert_equals([], $this->limit(null, null)); } public function test_fetch_no_results() diff --git a/test/helpers/DatabaseLoader.php b/test/helpers/DatabaseLoader.php index ec29828fa..f39568fb9 100644 --- a/test/helpers/DatabaseLoader.php +++ b/test/helpers/DatabaseLoader.php @@ -7,7 +7,7 @@ class DatabaseLoader { private $db; - static $instances = array(); + public static $instances = []; public function __construct($db) { @@ -61,7 +61,7 @@ public function exec_sql_script($file) public function get_fixture_tables() { - $tables = array(); + $tables = []; foreach (glob(__DIR__ . '/../fixtures/*.csv') as $file) { $info = pathinfo($file); @@ -88,14 +88,14 @@ public function load_fixture_data($table) $fields = fgetcsv($fp, escape: ""); if (!empty($fields)) { - $markers = join(',', array_fill(0, count($fields), '?')); + $markers = implode(',', array_fill(0, count($fields), '?')); $table = $this->quote_name($table); foreach ($fields as &$name) { $name = $this->quote_name(trim($name)); } - $fields = join(',', $fields); + $fields = implode(',', $fields); while (($values = fgetcsv($fp, escape: ""))) { $this->db->query("INSERT INTO $table($fields) VALUES($markers)", $values); diff --git a/test/helpers/DatabaseTest.php b/test/helpers/DatabaseTest.php index 41a6f4429..2a7985744 100644 --- a/test/helpers/DatabaseTest.php +++ b/test/helpers/DatabaseTest.php @@ -8,7 +8,7 @@ use ActiveRecord\ConnectionManager; use ActiveRecord\Exceptions\DatabaseException; use ActiveRecord\Exceptions\UndefinedPropertyException; - + class DatabaseTest extends SnakeCase_PHPUnit_Framework_TestCase { protected $conn; @@ -88,15 +88,15 @@ public function assert_exception_message_contains($contains, $closure) */ public function assert_sql_has($needle, $haystack) { - $needle = str_replace(array('"','`'), '', $needle); - $haystack = str_replace(array('"','`'), '', $haystack); + $needle = str_replace(['"','`'], '', $needle); + $haystack = str_replace(['"','`'], '', $haystack); return $this->assertStringContainsString($needle, $haystack); } public function assert_sql_doesnt_has($needle, $haystack) { - $needle = str_replace(array('"','`'), '', $needle); - $haystack = str_replace(array('"','`'), '', $haystack); + $needle = str_replace(['"','`'], '', $needle); + $haystack = str_replace(['"','`'], '', $haystack); return $this->assertStringNotContainsString($needle, $haystack); } diff --git a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php index 4357709ab..3f4306b82 100644 --- a/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php +++ b/test/helpers/SnakeCase_PHPUnit_Framework_TestCase.php @@ -19,7 +19,7 @@ public function __call($meth, $args) $class_name = get_called_class(); $trace = debug_backtrace(); - die("PHP Fatal Error: Call to undefined method $class_name::$meth() in {$trace[1]['file']} on line {$trace[1]['line']}". PHP_EOL); + die("PHP Fatal Error: Call to undefined method $class_name::$meth() in {$trace[1]['file']} on line {$trace[1]['line']}" . PHP_EOL); } private function setup_assert_keys($args) @@ -27,12 +27,12 @@ private function setup_assert_keys($args) $last = count($args) - 1; $keys = array_slice($args, 0, $last); $array = $args[$last]; - return array($keys,$array); + return [$keys,$array]; } public function assert_has_keys(/* $keys..., $array */) { - list($keys,$array) = $this->setup_assert_keys(func_get_args()); + [$keys,$array] = $this->setup_assert_keys(func_get_args()); $this->assert_not_null($array, 'Array was null'); @@ -43,7 +43,7 @@ public function assert_has_keys(/* $keys..., $array */) public function assert_doesnt_has_keys(/* $keys..., $array */) { - list($keys,$array) = $this->setup_assert_keys(func_get_args()); + [$keys,$array] = $this->setup_assert_keys(func_get_args()); foreach ($keys as $name) { $this->assert_array_not_has_key($name, $array); diff --git a/test/helpers/config.php b/test/helpers/config.php index b58c5e2f0..30fa40f89 100644 --- a/test/helpers/config.php +++ b/test/helpers/config.php @@ -34,16 +34,16 @@ ActiveRecord\Config::initialize(function ($cfg) { $cfg->set_model_directory(realpath(__DIR__ . '/../models')); $cfg->set_model_namespace("TestModels"); - $cfg->set_connections(array( + $cfg->set_connections([ 'mysql' => getenv('PHPAR_MYSQL') ?: 'mysql://test:test@127.0.0.1/test', 'pgsql' => getenv('PHPAR_PGSQL') ?: 'pgsql://test:test@127.0.0.1/test', - 'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db')); + 'sqlite' => getenv('PHPAR_SQLITE') ?: 'sqlite://test.db']); // Adapter the model-level tests run against: mysql (default), pgsql or sqlite. $cfg->set_default_connection(getenv('PHPAR_ADAPTER') ?: 'mysql'); if (class_exists('Log_file')) { // PEAR Log installed - $logger = new Log_file(dirname(__FILE__) . '/../log/query.log', 'ident', array('mode' => 0664, 'timeFormat' => '%Y-%m-%d %H:%M:%S')); + $logger = new Log_file(dirname(__FILE__) . '/../log/query.log', 'ident', ['mode' => 0664, 'timeFormat' => '%Y-%m-%d %H:%M:%S']); $cfg->set_logging(true); $cfg->set_logger($logger); diff --git a/test/models/Amenity.php b/test/models/Amenity.php index f1a02518b..cc1141542 100644 --- a/test/models/Amenity.php +++ b/test/models/Amenity.php @@ -6,10 +6,10 @@ class Amenity extends Model { - static $table_name = 'amenities'; - static $primary_key = 'amenity_id'; + public static $table_name = 'amenities'; + public static $primary_key = 'amenity_id'; - static $has_many = [ + public static $has_many = [ 'property_amenities' ]; } diff --git a/test/models/Author.php b/test/models/Author.php index bd8e4fa29..e8a668b6c 100644 --- a/test/models/Author.php +++ b/test/models/Author.php @@ -6,15 +6,13 @@ class Author extends Model { - static $pk = 'author_id'; -// static $has_one = array(array('awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'), -// array('parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id')); - static $has_many = ['books']; - static $has_one = [ + public static $pk = 'author_id'; + public static $has_many = ['books']; + public static $has_one = [ ['awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'], ['parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id'] ]; - static $belongs_to = []; + public static $belongs_to = []; public function set_password($plaintext): void { @@ -27,7 +25,7 @@ public function set_name($value): void $this->assign_attribute('name', $value); } - public function return_something(): Array + public function return_something(): array { return ["sharks" => "lasers"]; } diff --git a/test/models/AuthorAttrAccessible.php b/test/models/AuthorAttrAccessible.php index 7b712f778..7ac8261e1 100644 --- a/test/models/AuthorAttrAccessible.php +++ b/test/models/AuthorAttrAccessible.php @@ -6,16 +6,16 @@ class AuthorAttrAccessible extends Model { - static $pk = 'author_id'; - static $table_name = 'authors'; - static $has_many = [ + public static $pk = 'author_id'; + public static $table_name = 'authors'; + public static $has_many = [ ['books', 'class_name' => 'BookAttrProtected', 'foreign_key' => 'author_id', 'primary_key' => 'book_id'] ]; - static $has_one = [ + public static $has_one = [ ['parent_author', 'class_name' => 'AuthorAttrAccessible', 'foreign_key' => 'parent_author_id', 'primary_key' => 'author_id'] ]; - static $belongs_to = []; + public static $belongs_to = []; // No attributes should be accessible - static $attr_accessible = [null]; + public static $attr_accessible = [null]; } diff --git a/test/models/AwesomePerson.php b/test/models/AwesomePerson.php index 415dedea3..c43e28633 100644 --- a/test/models/AwesomePerson.php +++ b/test/models/AwesomePerson.php @@ -6,5 +6,5 @@ class AwesomePerson extends Model { - static $belongs_to = ['author']; + public static $belongs_to = ['author']; } diff --git a/test/models/Biz/Newsletter.php b/test/models/Biz/Newsletter.php index c1945068c..bb797e892 100644 --- a/test/models/Biz/Newsletter.php +++ b/test/models/Biz/Newsletter.php @@ -6,8 +6,8 @@ class Newsletter extends Model { - static $has_many = array( - array('user_newsletters'), - array('users', 'through' => 'user_newsletters'), - ); + public static $has_many = [ + ['user_newsletters'], + ['users', 'through' => 'user_newsletters'], + ]; } diff --git a/test/models/Biz/User.php b/test/models/Biz/User.php index f3a72d6f2..db3e41563 100644 --- a/test/models/Biz/User.php +++ b/test/models/Biz/User.php @@ -6,8 +6,8 @@ class User extends Model { - static $has_many = array( - array('user_newsletters'), - array('newsletters', 'through' => 'user_newsletters') - ); + public static $has_many = [ + ['user_newsletters'], + ['newsletters', 'through' => 'user_newsletters'] + ]; } diff --git a/test/models/Biz/UserNewsletter.php b/test/models/Biz/UserNewsletter.php index 2c0b5c6f5..2a9b703ab 100644 --- a/test/models/Biz/UserNewsletter.php +++ b/test/models/Biz/UserNewsletter.php @@ -6,8 +6,8 @@ class UserNewsletter extends Model { - static $belong_to = array( - array('user'), - array('newsletter'), - ); + public static $belong_to = [ + ['user'], + ['newsletter'], + ]; } diff --git a/test/models/Book.php b/test/models/Book.php index 2603efebd..f20f9e892 100644 --- a/test/models/Book.php +++ b/test/models/Book.php @@ -6,9 +6,9 @@ class Book extends Model { - static $belongs_to = ['author']; - static $has_one = []; - static $use_custom_get_name_getter = false; + public static $belongs_to = ['author']; + public static $has_one = []; + public static $use_custom_get_name_getter = false; public function upper_name() { diff --git a/test/models/BookAttrAccessible.php b/test/models/BookAttrAccessible.php index c3fae6463..88824c1c8 100644 --- a/test/models/BookAttrAccessible.php +++ b/test/models/BookAttrAccessible.php @@ -6,9 +6,9 @@ class BookAttrAccessible extends Model { - static $pk = 'book_id'; - static $table_name = 'books'; + public static $pk = 'book_id'; + public static $table_name = 'books'; - static $attr_accessible = ['author_id']; - static $attr_protected = ['book_id']; + public static $attr_accessible = ['author_id']; + public static $attr_protected = ['book_id']; } diff --git a/test/models/BookAttrProtected.php b/test/models/BookAttrProtected.php index efd66944c..ca5a3d339 100644 --- a/test/models/BookAttrProtected.php +++ b/test/models/BookAttrProtected.php @@ -6,12 +6,12 @@ class BookAttrProtected extends Model { - static $pk = 'book_id'; - static $table_name = 'books'; - static $belongs_to = [ + public static $pk = 'book_id'; + public static $table_name = 'books'; + public static $belongs_to = [ ['author', 'class_name' => 'AuthorAttrAccessible', 'primary_key' => 'author_id'] ]; // No attributes should be accessible - static $attr_accessible = [null]; + public static $attr_accessible = [null]; } diff --git a/test/models/Employee.php b/test/models/Employee.php index fde60c4d8..915e2e79b 100644 --- a/test/models/Employee.php +++ b/test/models/Employee.php @@ -6,5 +6,5 @@ class Employee extends Model { - static $has_one; + public static $has_one; } diff --git a/test/models/Event.php b/test/models/Event.php index 960a09ac2..1cff1b874 100644 --- a/test/models/Event.php +++ b/test/models/Event.php @@ -6,12 +6,12 @@ class Event extends Model { - static $belongs_to = [ + public static $belongs_to = [ 'host', 'venue' ]; - static $delegate = [ + public static $delegate = [ ['state', 'address', 'to' => 'venue'], ['name', 'to' => 'host', 'prefix' => 'woot'] ]; diff --git a/test/models/Host.php b/test/models/Host.php index 414e101ab..f6754ad1c 100644 --- a/test/models/Host.php +++ b/test/models/Host.php @@ -6,7 +6,7 @@ class Host extends Model { - static $has_many = [ + public static $has_many = [ 'events', ['venues', 'through' => 'events'] ]; diff --git a/test/models/JoinAuthor.php b/test/models/JoinAuthor.php index 556da98a5..e70268bc5 100644 --- a/test/models/JoinAuthor.php +++ b/test/models/JoinAuthor.php @@ -6,6 +6,6 @@ class JoinAuthor extends Model { - static $table_name = 'authors'; - static $pk = 'author_id'; + public static $table_name = 'authors'; + public static $pk = 'author_id'; } diff --git a/test/models/JoinBook.php b/test/models/JoinBook.php index bfc3bcfdf..1142cbed3 100644 --- a/test/models/JoinBook.php +++ b/test/models/JoinBook.php @@ -6,7 +6,7 @@ class JoinBook extends Model { - static $table_name = 'books'; + public static $table_name = 'books'; - static $belongs_to = []; + public static $belongs_to = []; } diff --git a/test/models/NamespaceTest/Book.php b/test/models/NamespaceTest/Book.php index c96c60837..d921940c0 100644 --- a/test/models/NamespaceTest/Book.php +++ b/test/models/NamespaceTest/Book.php @@ -6,14 +6,14 @@ class Book extends Model { - static $belongs_to = array( - array('parent_book', 'class_name' => '\TestModels\NamespaceTest\Book'), - array('parent_book_2', 'class_name' => 'Book'), - array('parent_book_3', 'class_name' => '\TestModels\Book'), - ); + public static $belongs_to = [ + ['parent_book', 'class_name' => '\TestModels\NamespaceTest\Book'], + ['parent_book_2', 'class_name' => 'Book'], + ['parent_book_3', 'class_name' => '\TestModels\Book'], + ]; - static $has_many = array( - array('pages', 'class_name' => '\TestModels\NamespaceTest\SubNamespaceTest\Page'), - array('pages_2', 'class_name' => 'SubNamespaceTest\Page'), - ); + public static $has_many = [ + ['pages', 'class_name' => '\TestModels\NamespaceTest\SubNamespaceTest\Page'], + ['pages_2', 'class_name' => 'SubNamespaceTest\Page'], + ]; } diff --git a/test/models/NamespaceTest/SubNamespaceTest/Page.php b/test/models/NamespaceTest/SubNamespaceTest/Page.php index 9879e98b9..ebe3baf58 100644 --- a/test/models/NamespaceTest/SubNamespaceTest/Page.php +++ b/test/models/NamespaceTest/SubNamespaceTest/Page.php @@ -6,7 +6,7 @@ class Page extends Model { - static $belong_to = array( - array('book', 'class_name' => '\TestModels\NamespaceTest\Book'), - ); + public static $belong_to = [ + ['book', 'class_name' => '\TestModels\NamespaceTest\Book'], + ]; } diff --git a/test/models/Position.php b/test/models/Position.php index 1bbd8cb11..cc446d966 100644 --- a/test/models/Position.php +++ b/test/models/Position.php @@ -6,5 +6,5 @@ class Position extends Model { - static $belongs_to; + public static $belongs_to; } diff --git a/test/models/Property.php b/test/models/Property.php index ae33e9e6f..d02a3d325 100644 --- a/test/models/Property.php +++ b/test/models/Property.php @@ -6,10 +6,10 @@ class Property extends Model { - static $table_name = 'property'; - static $primary_key = 'property_id'; + public static $table_name = 'property'; + public static $primary_key = 'property_id'; - static $has_many = [ + public static $has_many = [ 'property_amenities', ['amenities', 'through' => 'property_amenities'] ]; diff --git a/test/models/PropertyAmenity.php b/test/models/PropertyAmenity.php index 55c5e9561..6194fabda 100644 --- a/test/models/PropertyAmenity.php +++ b/test/models/PropertyAmenity.php @@ -6,10 +6,10 @@ class PropertyAmenity extends Model { - static $table_name = 'property_amenities'; - static $primary_key = 'id'; + public static $table_name = 'property_amenities'; + public static $primary_key = 'id'; - static $belongs_to = [ + public static $belongs_to = [ 'amenity', 'property' ]; diff --git a/test/models/Publisher.php b/test/models/Publisher.php index b9f383ecb..7fb67772e 100644 --- a/test/models/Publisher.php +++ b/test/models/Publisher.php @@ -6,11 +6,11 @@ class Publisher extends Model { - static $pk = 'publisher_id'; - static $cache = true; - static $cache_expire = 2592000; // 1 month. 60 * 60 * 24 * 30 + public static $pk = 'publisher_id'; + public static $cache = true; + public static $cache_expire = 2592000; // 1 month. 60 * 60 * 24 * 30 - static $has_many = [ + public static $has_many = [ 'authors' ]; } diff --git a/test/models/RmBldg.php b/test/models/RmBldg.php index 256615aa1..84000f328 100644 --- a/test/models/RmBldg.php +++ b/test/models/RmBldg.php @@ -6,31 +6,31 @@ class RmBldg extends Model { - static $table = 'rm-bldg'; + public static $table = 'rm-bldg'; - static $validates_presence_of = [ + public static $validates_presence_of = [ ['space_out', 'message' => 'is missing!@#'], ['rm_name'] ]; - static $validates_length_of = [ + public static $validates_length_of = [ ['space_out', 'within' => [1, 5]], ['space_out', 'minimum' => 9, 'too_short' => 'var is too short!! it should be at least %d long'] ]; - static $validates_inclusion_of = [ + public static $validates_inclusion_of = [ ['space_out', 'in' => ['jpg', 'gif', 'png'], 'message' => 'extension %s is not included in the list'], ]; - static $validates_exclusion_of = [ + public static $validates_exclusion_of = [ ['space_out', 'in' => ['jpeg']] ]; - static $validates_format_of = [ + public static $validates_format_of = [ ['space_out', 'with' => '/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i'] ]; - static $validates_numericality_of = [ + public static $validates_numericality_of = [ ['space_out', 'less_than' => 9, 'greater_than' => '5'], ['rm_id', 'less_than' => 10, 'odd' => null] ]; diff --git a/test/models/Venue.php b/test/models/Venue.php index f79c9c4ed..a6d75a707 100644 --- a/test/models/Venue.php +++ b/test/models/Venue.php @@ -6,17 +6,17 @@ class Venue extends Model { - static $use_custom_get_state_getter = false; - static $use_custom_set_state_setter = false; + public static $use_custom_get_state_getter = false; + public static $use_custom_set_state_setter = false; - static $has_many = [ + public static $has_many = [ 'events', ['hosts', 'through' => 'events'] ]; - static $has_one; + public static $has_one; - static $alias_attribute = [ + public static $alias_attribute = [ 'marquee' => 'name', 'mycity' => 'city' ]; diff --git a/test/models/VenueAfterCreate.php b/test/models/VenueAfterCreate.php index 82fa1205f..dc076f5c1 100644 --- a/test/models/VenueAfterCreate.php +++ b/test/models/VenueAfterCreate.php @@ -6,8 +6,8 @@ class VenueAfterCreate extends Model { - static $table_name = 'venues'; - static $after_create = ['change_name_after_create_if_name_is_change_me']; + public static $table_name = 'venues'; + public static $after_create = ['change_name_after_create_if_name_is_change_me']; public function change_name_after_create_if_name_is_change_me() { diff --git a/test/models/VenueCB.php b/test/models/VenueCB.php index 1725ceb47..b6c16a621 100644 --- a/test/models/VenueCB.php +++ b/test/models/VenueCB.php @@ -6,14 +6,14 @@ class VenueCB extends Model { - static $table_name = 'venues'; - static $before_save; - static $before_update; - static $before_create; - static $before_validation; - static $before_destroy = 'before_destroy_using_string'; - static $after_destroy = ['after_destroy_one', 'after_destroy_two']; - static $after_create; + public static $table_name = 'venues'; + public static $before_save; + public static $before_update; + public static $before_create; + public static $before_validation; + public static $before_destroy = 'before_destroy_using_string'; + public static $after_destroy = ['after_destroy_one', 'after_destroy_two']; + public static $after_create; // DO NOT add a static $after_construct for this. we are testing // auto registration of callback with this From c517680899e2e8414dc24c3982122d03ea3e489b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Miguel=20P=C3=A9rez?= Date: Thu, 10 Sep 2026 02:38:45 +0200 Subject: [PATCH 12/12] Gate CI on phpcs and stop duplicate workflow runs Add a "Coding standard" job that runs vendor/bin/phpcs with the rules from phpcs.xml, so a pull request that breaks the standard fails CI. Trigger the workflow on pushes to master and on pull requests only. With both events unfiltered, every push to a branch with an open pull request started two identical runs. A concurrency group cancels a run that a newer push to the same branch or pull request has superseded. --- .github/workflows/tests.yml | 24 ++++++++++++++++++++++++ CONTRIBUTING.md | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f847eba4..412e047bd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,8 +2,14 @@ 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' }} @@ -99,3 +105,21 @@ jobs: 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/CONTRIBUTING.md b/CONTRIBUTING.md index 7a9c470b8..08ea75e3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,4 +63,5 @@ You also need the PHP extensions `pdo_mysql`, `pdo_pgsql`, `pdo_sqlite` and `mem `.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. Update the matrix when a PHP or database version reaches end of life. +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.