I only found the code for getRelation($name) in ActiveRecord.
But how can I access the relations list from a model, like $model->relations()?
Try $model->populatedRelations
There is no list of all available relations as they are defined via getters and we only know whether a getter represents a relation or not when we call it. What's your use case?
My current use-case: creating CRUDs with relations.
you could check all getters via Reflection and check whether they return an object that implements the relation interface.
I'd thought about that, but I found this a bit heavy for Yii, no?
I'd really like to see this in ActiveRecord.
IMHO this is very important information for this type of an object.
If you do this for code generation it should be fine, can't think of any other use case. You can get all populated relations via populatedRelations when they have been populated using with before in case you want to return a model and its relations in an API for example.
In all cases I can think of you are using a relation because you know it exists and want its data. Normally you also do not care whether it is a relation behind that or not you just get a specific property of the model regardless how the model gets the data it gives to you.
I don't think it is very important information as a relation is just another property of the model. You can easily get the list of all properties but you hardly ever need to know its type.
I was thinking about it a lot and must agree with @cebe that the only moment when a list of relations is needed is the generator.
I bet someone can make a model template that will make a method returning all relation names, same as in Yii 1. But that's totally up to the user.
What's about a CRUD which is dynamically generated at runtime? I think there was even an extension for that in Yii 1.
Can I populate all relations of a record at once?
@schmunk42 yes but you need to know their names.
ModelName::find()->with(['relation1', 'relation2', ...])->one();
If you have such special needs you could define a method like relations() that will give you a list that you manually maintain.
Sure, but you need to extend from that model then.
But I think it's OK to go with the reflection approach.
Thanks for your input.
Just to let you know, this is my current code:
public function getModelRelations(){
$reflector = new \ReflectionClass($this->modelClass);
$model = new $this->modelClass;
$stack = array();
foreach ($reflector->getMethods() AS $method) {
if (substr($method->name,0,3) !== 'get') continue;
if ($method->name === 'getRelation') continue;
if ($method->name === 'getBehavior') continue;
if ($method->name === 'getFirstError') continue;
if ($method->name === 'getAttribute') continue;
if ($method->name === 'getAttributeLabel') continue;
if ($method->name === 'getOldAttribute') continue;
$relation = call_user_func(array($model,$method->name));
if($relation instanceof yii\db\ActiveRelation) {
$stack[] = $relation;
}
}
return $stack;
}
Please let me know if you see a better way to get the relations.
To know the type of the return value of a method I have to call it, but some getter require an argument.
Maybe with a try/catch statement?
You may check the number of parameters as a relation always has 0:
http://www.php.net/manual/en/reflectionfunctionabstract.getnumberofparameters.php
$baseClassMethods = get_class_methods('yii\db\ActiveRecord');
foreach ($reflector->getMethods() as $method) {
if (in_array($method->name, $baseClassMethods)) {
continue;
}
$relation = call_user_func(array($model,$method->name));
if($relation instanceof yii\db\ActiveRelation) {
$stack[] = $relation;
}
}
Yii 2.0.5 code on my trait https://github.com/mootensai/yii2-relation-trait
public function getRelationData() {
$ARMethods = get_class_methods('\yii\db\ActiveRecord');
$modelMethods = get_class_methods('\yii\base\Model');
$reflection = new \ReflectionClass($this);
$i = 0;
$stack = [];
/* @var $method \ReflectionMethod */
foreach ($reflection->getMethods() as $method) {
if (in_array($method->name, $ARMethods) || in_array($method->name, $modelMethods)) {
continue;
}
if($method->name === 'bindModels') {continue;}
if($method->name === 'attachBehaviorInternal') {continue;}
if($method->name === 'getRelationData') {continue;}
try {
$rel = call_user_func(array($this,$method->name))
if($rel instanceof \yii\db\ActiveQuery){
$stack[$i]['name'] = lcfirst(str_replace('get', '', $method->name));
$stack[$i]['method'] = $method->name;
$stack[$i]['ismultiple'] = $rel->multiple;
$i++;
}
} catch (\yii\base\ErrorException $exc) {
//
}
}
return $stack;
}
Maybe this was an alternative method.
public function getRelations($class) {
if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
$tableSchema = $class::getTableSchema();
$foreignKeys = $tableSchema->foreignKeys;
$relations = [];
foreach ($foreignKeys as $key => $value) {
$splitedNames = explode('_', $value[0]);
foreach ($splitedNames as $name) {
$relations[$key] .= ucfirst($name);
}
}
return $relations;
}
}
$baseClassMethods = get_class_methods('yii\db\ActiveRecord'); foreach ($reflector->getMethods() as $method) { if (in_array($method->name, $baseClassMethods)) { continue; } $relation = call_user_func(array($model,$method->name)); if($relation instanceof yii\db\ActiveRelation) { $stack[] = $relation; } }
@klimov-paul Sorry but there isnt any class name yii\db\ActiveRelation that you are using , did you wanted to use yii\db\ActiveQueryInterface instead at the line if($relation instanceof yii\db\ActiveRelation) {
also the call_user_func(array($model,$method->name)); will raise error in case there are custom methods defined in the model and they require passing a parameter
The way I think about doing it is by setting a return type to my relation methods. Something like this:
public function getCompany(): ActiveQueryInterface
{
return $this->hasOne(Companies::class, ['id' => 'company_id']);
}
public function getRelationList()
{
$reflection = new \ReflectionClass($this);
foreach ($reflection->getMethods() as $method) {
//only check methods beginning with get
if ((string)$method->getReturnType() !== ActiveQueryInterface::class) {
continue;
}
yield lcfirst(str_replace('get', '', $method->name));
}
}
Most helpful comment