Skip to content

1.x -> 2.x

2.x has a number of breaking API changes to be aware of. First, be aware that the minimum required PHP version is now 8.1. Other than that, there are some key API changes to be aware of.

Methods

Model::find

Much of the old find functionality has been moved to Relation. Most notably, the conditions argument is no longer supported, and has been replaced by where():

// 1.x
$books = Book::find([1,2,3], ['conditions'=>['title = ?', 'Walden']]);

// 2.0
$books = Book::where('title = ?', 'Walden')->to_a();

Also, in 2.x we have fixed a bug around calling find with an empty array. It will now throw a RecordNotFound exception, as it should. If you were relying on the old behavior, you should switch to where:

// 1.x
$ids = [];
$books = Book::find($ids);

// 2.0
$books::find($ids); // danger! now throws RecordNotFound
$books = Book::where(['book_id' => $ids])->to_a(); // this is okay

The all argument has been removed in favor of Relation::all (which does nothing but return a relation object), or `Relation::where():

// 1.x
$books = Book::find('all', ['conditions'=>['title = ?', 'Walden']]);

// 2.0
$books = Book::all()->where('title = ?', 'Walden')->to_a();

The same goes for first and last:

// 1.x
$book = Book::find('first');
$book = Book::find('last');

// 2.0
$book = Book::first();
$book = Book::last();

Model::all

Model::all() no longer takes any arguments, and now returns an iterable Relation instead of an array of models:

// 1.x
$books = Book::all(['conditions'=>['title = ?', 'Walden']]);
foreach($books as $book) {}

// 2.0
$books = Book::where('title = ?', 'Walden')->all();
foreach($books as $book) {}

The all argument has been removed in favor of Relation::all:

// 1.x
$books = Book::find('all', ['conditions'=>['title = ?', 'Walden']]);

// 2.0
$books = Book::all()->where('title = ?', 'Walden')->to_a();

Model::count

The changes to count mirror the changes to find:

// 1.x
$numBooks = Book::count(['conditions'=>['title = ?', 'Walden']]);

// 2.0
$books = Book::where('title = ?', 'Walden')->count();

Model::delete_all

Likewise for delete_all:

// 1.x
$numDeleted = Book::delete_all(['conditions'=>['title = ?', 'Walden']]);

// 2.0
$numDeleted = Book::where('title = ?', 'Walden')->delete_all();

Model::update_all

update_all has undergone a similar transformation, and now simply takes a string or hash of attributes as an argument:

// 1.x
$numUpdated = Book::update_all([
  'conditions'=>['title = ?', 'Walden'],
  'set' => ['author_id' => 1]
]);

// 2.0
$numUpdated = Book::where(['title = ?', 'Walden'])->update_all([
  'author_id' => 1
]);

Model::find_all_by_<attribute>

The find_all_by... style of magic method has been removed entirely.

// 1.x
$books = Book::find_all_by_title('Ubik');

// 2.0
$books = Book::where('title = ?', 'Ubik')->to_a();

Model::table

The static table accessor on Model is now protected. If you were making calls directly on Table, you will need to refactor your code.

// 1.x
Book::table()->update($attributes, $where);

// 2.0
Book::where($where)->update_all($attributes);

If you do need access to the table instance for some reason, you can still get to it:

  $table = Table::load(Book::class);

Static Properties

The static relationship properties have changed shape, moving from a flat array to a key-config format:

Model::$has_one

// 1.x
class Author extends ActiveRecord 
{
  static $has_one =  [
    ['awesome_person', 'foreign_key' => 'author_id', 'primary_key' => 'author_id'],
    ['parent_author', 'class_name' => 'Author', 'foreign_key' => 'parent_author_id']];
}

// 2.0
class Author extends ActiveRecord 
{
  static $has_one =  [
    'awesome_person' => [
      'foreign_key' => 'author_id', 
      'primary_key' => 'author_id'
    ],
    [
      'parent_author' => [
        'class_name' => 'Author', 
        'foreign_key' => 'parent_author_id'
        ]
    ]
  ];
}

Model::$has_many

// 1.x
class Person extends ActiveRecord 
{
  static $has_many = [
    ['children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'],
    ['orders']
  ];
}

// 2.0
class Person extends ActiveRecord 
{
  static $has_many = array(
    [
      'children' => [
        'foreign_key' => 'parent_id', 
        'class_name' => 'Person'
      ],
      'orders' => true
   ];
}

Model::$belongs_to

// 1.x
class Person extends ActiveRecord 
{
  static $belongs_to = [
    ['parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person'],
    ['orders']
  ];
}

// 2.0
class Person extends ActiveRecord 
{
  static $belongs_to = array(
    [
      'parent' => [
        'foreign_key' => 'parent_id', 
        'class_name' => 'Person'
      ],
   ];
}

Model::$validates_inclusion_of

(note: the same changes apply for Model::$validates_exclusion_of)

// 1.x
class Book extends ActiveRecord 
{
  public static $validates_exclusion_of = [
        ['name', 'in' => ['blah', 'alpha', 'bravo']]
    ];
}

// 2.0
class Book extends ActiveRecord 
{
  public static $validates_exclusion_of = [
    'name' => [
      'in' => ['blah', 'alpha', 'bravo']]
    ];
}

Other Changes

Table::update

You generally shouldn't be working directly with a Table instance, but if you are you should be aware that the update method has changed shape:

// 1.x 
$table = Book::table();
$table->update([ 'title' => 'Walden` ], ['author_id` => 1]);

// 2.0
$table = Table::load(Book::class);
$options = [
    'conditions' => [new WhereClause(['author_id` => 1])]
];
$table->update([ 'title' => 'Walden' ], $options); // where $options is a RelationOptions

Table::delete

You generally shouldn't be working directly with a Table instance, but if you are you should be aware that the delete method has changed shape:

// 1.x 
$table = Book::table();
$table->delete(['author_id' => 1]);

// 2.0
$table = Table::load(Book::class);
$options = [
    'conditions' => [new WhereClause(['author_id` => 1])]
];
$table->delete($options); // where $options is a RelationOptions.

Config::set_model_directory

Config::set_model_directory has been removed, meaning that the active record library no longer maintains its own autoloader or knowledge of where your models are kept. In 2.0 it is recommended to use your own autoloader to manage your models as you would any other classes in your project.

When setting up relationships, active record will assume that any associations are in the same namespace as the class they are bound to. Alternatively, you can specify a class_name in the config options.

Any of the following should work fine:

// 2.0
namespace test\models;

use ActiveRecord\Model;

class Author extends Model
{
    public static array $has_many = [
        'books' => true // will attempt to load from test\models
    ];

    public static array $has_one = [
        'parent_author' => [
            'class_name' => Author::class, 
            'foreign_key' => 'parent_author_id'
        ]
    ];

}

exceptions location

All crafted exceptions have moved to a new home in the Exceptions namespace.

// 1.x
use ActiveRecord\RecordNotFound

try {
...
}
catch(RecordNotFound)

// 2.0
use ActiveRecord\Exception\RecordNotFound

try {
...
}
catch(RecordNotFound)