Git Product home page Git Product logo

yii2-activerecord-inheritance's Introduction

ActiveRecord Inheritance

ActiveRecord Inheritance is a util to provide the Class Table Inheritance Pattern to the Yii2 framework. Its motivation is to fake inheritance between two ActiveRecord classes.

Installation

Include the package as dependency under the bower.json file.

To install, either run

$ php composer.phar require jlorente/yii2-activerecord-inheritance "*"

or add

...
    "require": {
        ...
        "jlorente/yii2-activerecord-inheritance": "*"
    }

to the require section of your composer.json file.

Usage

An example of usage could be:

Suppose you have the following schema.

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `last_name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` int(11) NOT NULL,
  `updated_at` int(11) NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `level` INT NOT NULL,
  `banned_users` INT NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  CONSTRAINT `FK_Admin_Id` FOREIGN KEY (`id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
);

To fake the inheritance between the two tables your code must look like this.

use jlorente\db\ActiveRecordInheritanceTrait,
    jlorente\db\ActiveRecordInheritanceInterface;
use yii\db\ActiveRecord;

class User extends ActiveRecord {

    public function tableName() {
        return 'user';
    }

    public function doSomething() {
        echo 'User does something';
    }
}

class Admin extends ActiveRecord implements ActiveRecordInheritanceInterface {
    use ActiveRecordInheritanceTrait;

    public function tableName() {
        return 'admin';
    }

    public static function extendsFrom() {
        return User::className();
    }

    public function doSomething() {
        echo 'Admin does something';
    }
}

And you will be able to use Admin objects as if they were inheriting from User objects.

$admin = new Admin();
$admin->username = 'al-acran';
$admin->email = '[email protected]';
$admin->name = 'Al';
$admin->last_name = 'Acran';
$admin->level = 1;
$admin->save();

You can call parent methods and properties by using the parent relation property.

$admin->doSomething()           //Admin does something
$admin->parent->doSomething()   //User does something

This trait is very useful for faking inheritance, however query filters should be applied on the parent relation.

$admin = Admin::find()
    ->joinWith('parent', function($query) {
        $query->andWhere(['username' => 'al-acran']);
        $query->andWhere(['name' => 'Al']);
    })
    ->andWhere(['level' => 1])
    ->one();

Considerations

In order to use the trait properly, you must consider the following points

General

  • By default, the primary key of the supposed child class is used as foreign key of the parent model, if you want to use another attribute as foreign key, yoy should overwrite the parentAttribute() method.
  • The trait won't work with multiple primary and foreign keys.

ActiveRecord methods

  • Methods overwriten from ActiveRecord in this trait like save, validate, etc... should not be overwriten on the class that uses the trait or functionality will be lost.
  • To overwrite this methods properly, you could extend the class that uses the trait and overwrite there the methods, making sure that all methods call its parent implementation.

Inheritance call hierarchy

  • The natural call hierarchy is preserved, so if a method or property exists on the called class or one of its ancestors, the method or property is called.
  • If a method or property doesn't exist in the natural call hierarchy. The properly magic method is called (__get, __set, __isset, __unset, __call) and the yii2 call hierarchy is used. If the method or property isn't found in this call hierarchy the next step is executed.
  • The previous process is repeteated for the faked parent object.
  • The call hierarchy will stop when a method or property is found or when there are no more parents. In this case, an UnknownPropertyException or an UnknownMethodException will be raised.
  • You can concatenate faked parent classes with the only limit of the php call stack.

License

Copyright © 2015 José Lorente Martín [email protected]. Licensed under the MIT license. See LICENSE.txt for details.

yii2-activerecord-inheritance's People

Contributors

jlorente avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

yii2-activerecord-inheritance's Issues

getAttributes throws UnknownPropertyException

When calling getAttributes() with parameter $names and requesting a field of the child model, an UnknownPropertyException gets thrown, because parameter $names is used unmodified in $this->_parent()->getAttributes($names, $except)

It should be filtered to only contain field names of the parent model.

Not compatible with yii2 2.0.45

When trying to access a parent property for a newly created ActiveRecord, the whole server crashes without any error. It's working fine with the 2.0.44 version. I've tried with php 7.4 and 8.0, same results.

This access https://github.com/jlorente/yii2-activerecord-inheritance/blob/master/src/ActiveRecordInheritanceTrait.php#L95 causes the cash. While in the 2.0.44 version it returns null (because the entry is not saved yet) in 2.0.45 it crashes.

Changing the function to this might work:

if ($this->getIsNewRecord() === true) {
    $this->_parent = new $pClass();
} else {
    $parent = $this->_parent()->one();
    if($parent !== null)
        $this->_parent = $parent;
}

To reproduce:

$admin = new Admin();
$admin->username = 'al-acran'; <-- crash without any error

This works fine;

$admin = Admin::find()->one();
$admin->username = 'al-acran';

Any thoughts on this?

An extra query to the database on model.refresh()

Hello!
First of all thank you for your work!

I found a little issue. Trying explain it :)

If i refresh child model then i see that there are one extra query to parent table. Total 3 queries instead of 2.
I explored the source.

public function refresh() {
    $r = parent::refresh();
    return $this->_parent()->refresh() && $r;
}

$r = parent::refresh() do 2 queries:
query to child table and extra query to parent, Last query initiate by code:

public static function populateRecord($record, $row) {
parent::populateRecord($record, $row);

    _$record->_parent = $record->parent;_
}

Then
$this->_parent()->refresh() && $r do 1 query to parent

Hope you understand my explonation.

Bug in getFirstError

Last line of getFirstError() should be:
return count($errors) ? $errors[0] : null;
instead of
return empty($errors[$attribute]) ? null : $errors[0];

$errors is a numeric array.

Eager loading support

Hello
Your implementation of populateRecord() does not allow eager loading.
If we remove this implementation, the Trait continue to operate and increase its performance.

getAttributes not work correctly with multiple levels of inheritance

Hi @jlorente,

Testing your trait with cascading inheritance, getAttributes not work correctly. In this case only returns the values of the attributes of the model and model parent. Grandparent attributes are null.

I propose the following implementation to fix:

public function getAttributes($names = null, $except = []) {
    return array_merge($this->_parent()->getAttributes($names, $except), parent::getAttributes($names, $except));
}

regards...

Was not overridden parent attributes while calling parent method

class Parent extends ActiveRecord
{    
    $test = '123';

    public function text()
    {
        echo $this->test;
    }
}


class Child extends ActiveRecord implements ActiveRecordInheritanceInterface
{
    use ActiveRecordInheritanceTrait;
    
    $test = '456';

    public static function extendsFrom()
    {
        return Issue::className();
    }
}

$child = new Child();
$child->text();
// must be echo '456' but I got '123'

I was write solution, but it's bad solution. Put this into Child class.

    public function __call($name, $params) {
        try {
            return parent::__call($name, $params);
        } catch (UnknownMethodException $e) {
            foreach (get_object_vars($this) as $cur_obj_attr_name => $cur_obj_val) {
                foreach (get_object_vars($this->_parent()) as $parent_obj_attr_name => $parent_obj_val) {
                    if ($cur_obj_attr_name == $parent_obj_attr_name) {
                            $this->_parent()->$cur_obj_attr_name = $cur_obj_val;
                    }
                }
            }
            return call_user_func_array([$this->_parent(), $name], $params);
        }
    }

Filter child model by parent attributes

Hi!
Is there any way to filter a GridView by parent attributes?
I managed to do the sorting, but the filtering is not working. It's showing validation errors on each field even when no text has been entered.
Thanks in advance

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.