Yii2: Multiple inverse relations to same model? - yii2

How do I handle multiple inverse relations pointing to the same active record?
For example:
class Bicycle extends ActiveRecord {
public function getFrontWheel() {
return $this
->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
->inverseOf('bicycles');
}
public function getRearWheel() {
return $this
->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
->inverseOf('bicycles');
}
}
class Wheel extends ActiveRecord {
public function getBicycles() {
return $this
->hasMany(Bicycle::class, ['???' => 'id'])
->inverseOf('??????');
}
}
What can I do here? I critically need the inverse relations.

Here is my own solution.
Key points:
It all boils down to proper naming.
Inverse relations are bijective! In other words, every relation always has to have its own unique mirror relation on the other end.
class Bicycle extends ActiveRecord {
public function getFrontWheel() {
return $this
->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
->inverseOf('frontWheelBicycles');
}
public function getRearWheel() {
return $this
->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
->inverseOf('rearWheelBicycles');
}
}
class Wheel extends ActiveRecord {
public function getFrontWheelBicycles() {
return $this
->hasMany(Bicycle::class, ['front_wheel_id' => 'id'])
->inverseOf('frontWheel');
}
public function getRearWheelBicycles() {
return $this
->hasMany(Bicycle::class, ['rear_wheel_id' => 'id'])
->inverseOf('rearWheel');
}
}

i would suggest to do the following:
create two new classes:
class FrontWheel extends Wheel {
class RearWheel extends Wheel {
in new classes you can set easily the relation.
How to instantiate the correct class? There is a method in ActiveRecord instantiate() where you can write your logic which wheel class need to be created.
class Wheel extends ActiveRecord {
...
public static function instantiate ( $row ) {
if($row['type'] === 'RearWheel') {
return new RealWheel();
}
...
}
full code:
class Bicycle extends ActiveRecord
{
public function getFrontWheel()
{
return $this
->hasOne(Wheel::class, ['id' => 'front_wheel_id'])
->inverseOf('bicycles');
}
public function getRearWheel()
{
return $this
->hasOne(Wheel::class, ['id' => 'rear_wheel_id'])
->inverseOf('bicycles');
}
}
abstract class Wheel extends ActiveRecord
{
public static function instantiate($row)
{
if ($row['type'] === 'RearWheel') {
return new RealWheel();
}
if ($row['type'] === 'FrontWheel') {
return new FrontWheel();
}
throw new InvalidConfigException();
}
abstract public function getBicycles();
}
class RealWheel extends Wheel
{
public function getBicycles()
{
return $this
->hasMany(Bicycle::class, ['rear_wheel_id' => 'id'])
->inverseOf('rearWheel');
}
}
class FrontWheel extends Wheel
{
public function getBicycles()
{
return $this
->hasMany(Bicycle::class, ['front_wheel_id' => 'id'])
->inverseOf('frontWheel');
}
}

Related

Yii2 relationship hasOne

I have 2 simple models:
class Page extends ActiveRecord {
public static function tableName() {
return 'page';
}
public function getPageType() {
return $this->hasOne(PageType::className(), ['id', 'page_type_id']);
}
}
class PageType extends ActiveRecord {
public static function tableName() {
return 'page_type';
}
public function getPages() {
return $this->hasMany(Page::className(), ['page_type_id', 'id']);
}
}
I am trying to get a list of pages and filter them by page_type_id, so i am doing:
Page::find()
->joinWith('pageType')
->where(['page.service_id' => $this->serviceId, 'page.active' => 1])
->all();
But I am getting the following error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'page_type.0' in 'on clause'
The SQL being executed was: SELECT `page`.* FROM `page`
LEFT JOIN `page_type`
ON
`page`.`id` = `page_type`.`0`
AND
`page`.`page_type_id` = `page_type`.`1`
WHERE
(`page`.`service_id`=1) AND (`page`.`active`=1)
Any ideas?
You need to provide the column names in the relation as name=>value pairs, not comma-separated list.
return $this->hasOne(PageType::className(), ['id', 'page_type_id']);
and
return $this->hasMany(Page::className(), ['page_type_id', 'id']);
Change your relations and your classes code look like following
class Page extends ActiveRecord {
public static function tableName() {
return 'page';
}
public function getPageType() {
return $this->hasOne(PageType::className(), ['id'=> 'page_type_id']);
}
}
class PageType extends ActiveRecord {
public static function tableName() {
return 'page_type';
}
public function getPages() {
return $this->hasMany(Page::className(), ['page_type_id'=>'id']);
}
}

Yii2 access class from within the class

I'm trying to use the findOn from within the class that I want to search. Is this possible or is there a better way?
class CmsSettings extends ActiveRecord
{
public static function tableName()
{
return 'cms_settings';
}
//not working
public static function run(){
$results = CmsSettings::findOn(1):
return $results;
}
// not working
public static function check(){
$results = CmsSettings::findOn(1):
if($results->somesetting){
return true;
}
}
}
You should probably use static::findOne(1). By using self or CmsSettings you are just hardcoding returned type, which makes this class less flexible and will give you unexpected results on inheritance. For example if you create child model which extends your class:
class CmsSettings extends ActiveRecord {
public static function run() {
$results = CmsSettings::findOne(1);
return $results;
}
// ...
}
class ChildCmsSettings extends CmsSettings {
}
You expect that ChildCmsSettings::run() will return instance of ChildCmsSettings. Wrong - you will get CmsSettings. But if you write this method with using static:
public static function run() {
$results = static::findOne(1);
return $results;
}
You will get instance of class which you're used for call run() - ChildCmsSettings.
Use self
Refer findOne()
class CmsSettings extends ActiveRecord
{
public static function tableName()
{
return 'cms_settings';
}
public static function run()
{
$results = self::findOne(1);
return $results;
}
public static function check()
{
$results = self::findOne(1);
if ($results->somesetting) {
return true;
}
}
}

Class name dynamically in hasOne not working

public function getResource() {
return $this->hasOne(User::className(), ['id' => 'resource_id']);
}
this function working fine but when i use this
public function getResource() {
$model = ucfirst($this->resource_type);
return $this->hasOne($model::className(), ['id' => 'resource_id']);
}
its give me error "Class 'User' not found".
Thanks
you have to use the name including namespace if you specify it dynamically.
public function getResource() {
$model = "api\\models\\".ucfirst($this->resource_type);
return $this->hasOne($model::className(), ['id' => 'resource_id']);
}

custom validation in yii2 model

I am trying to add custom rule to form. I have added a custom function in model but it's not working for me.
class BackendUser extends ActiveRecord implements IdentityInterface
{
public function rules()
{
return [
['username','validateUsername','params'=>'username'=>'username']],
];
}
public function validateUsername($attribute, $params)
{
if (preg_match('/[^a-z])/i', $this->$attribute)) {
$this->addError($attribute, 'Username should only contain
alphabets');
}
}}
In PHP there is no construct like you used here (a => b => c, maybe it's a typo) and you don't have to pass any parameters anyway since you don't use them in the validator method. Simple
public function rules()
{
return [
['username','validateUsername'],
];
}
is enough.
There are few typos in your code. Try using $this->{$attribute} in dynamic attributes and also params key should be an array while calling inline validation.
class BackendUser extends ActiveRecord implements IdentityInterface
{
public function rules()
{
return [
['username','validateUsername','params'=>['username'=>'username']],
];
}
public function validateUsername($attribute, $params)
{
if (preg_match('/[^a-z])/i', $this->{$attribute})) {
$this->addError($attribute, 'Username should only contain alphabets');
}
}
}

$this->load function in Core file of codeigniter

I have this in all my Controllers for Menu, but I want to give it once in the core folder of CI Any Suggestions?
> $data['cms_menus']=$this->database_model->GetRecords('cms_menus',
> false, array('FKMenuID'=>null));
> foreach ($data['cms_menus'] as $key => $datas){
> $data['cms_menus'][$key]['childs']=$this->database_model->GetRecords('cms_menus',
> false, array('FKMenuID'=> $datas['PKMenuID']));
One way is to extend CI_Controller
class MY_Contoller extends CI_Controller
{
public $data;
public function __construct()
{
parent :: __construct();
$this->load->model('database_model');
$data['cms_menus']=$this->database_model->GetRecords('cms_menus', false, array('FKMenuID'=>null));
foreach ($data['cms_menus'] as $key => $datas)
{
$this->data['cms_menus'][$key]['childs']=$this->database_model->GetRecords('cms_menus', false, array('FKMenuID'=> $datas['PKMenuID']));
}
}
Use MY_Controller to define the rest of your controllers.
class Other_contoller extends MY_Controller
{
public function __construct()
{
parent :: __construct();
//other construct code here
}
public function index()
{
//use the menu in a view
$this->load->view('some_view', $this->data);
//The view can use $cms_menus as it is in $this->data
}
}