Problem with relations
b1rdex opened this issue ยท 7 comments
Code in the controller:
/**
* @var Transfer|null $transfer
*/
$transfer = Transfer::find()->one();
if (!$transfer) {
return;
}
$paymentsTransfers = $transfer->getPaymentTransfer()->all();
Model:
class Transfer extends \yii\db\ActiveRecord
{
/**
* @return \yii\db\ActiveQuery
*/
public function getPaymentTransfer()
{
return $this->hasMany(PaymentTransfer::class, ['transfer_id' => 'id']);
}
}
getPaymentTransfer()
method return type provided in PhpDoc or using native return type set to ActiveQuery
leads to extension exception: Internal error: Unexpected type PHPStan\Type\ObjectType during method call all at line 142
(line 142 is $transfer->getPaymentTransfer()->all()
call). That error comes from \Proget\PHPStan\Yii2\Type\ActiveQueryDynamicMethodReturnTypeExtension::getTypeFromMethodCall
. Here is what's in the $calledOnType
:
object(PHPStan\Type\ObjectType)#9382 (3) {
["className":"PHPStan\Type\ObjectType":private]=>
string(18) "yii\db\ActiveQuery"
["subtractedType":"PHPStan\Type\ObjectType":private]=>
NULL
["genericObjectType":"PHPStan\Type\ObjectType":private]=>
NULL
}
If I remove return type from getPaymentTransfer()
everything works fine. But that looks odd and that's easy to break if someone adds return type.
This is not limited to relations. I am facing this issue with simple methods returning yii\db\ActiveQuery
, such as:
public static function findBySomething(string $something): \yii\db\ActiveQuery
{
return self::find()
->where(['something' => $something]);
}
I've currently got a working version for
MyModel::findBySql("")->all();
and anything that returns an ActiveQuery
.
However, saving it to a variable is still a work-in-progress (because I do not know how to access the MyModel
part here):
$query = MyModel::findBySql("");
$query->all();
If anyone has any knowledge, there's an open question at phpstan.
Modified code - click to expand
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$methodName = $methodReflection->getName();
$calledOnType = $scope->getType($methodCall->var);
if (!$calledOnType instanceof ActiveQueryObjectType) {
if (!($calledOnType instanceof ObjectType) || $calledOnType->getClassName() !== ActiveQuery::class) {
throw new ShouldNotHappenException(sprintf('Unexpected type %s during method call %s at line %d', \get_class($calledOnType), $methodReflection->getName(), $methodCall->getLine()));
}
$var = $methodCall->var;
if ($var instanceof StaticCall) {
/**
* @example
* MyModel::findBySql("")->all();
*/
$calledOnType = new ActiveQueryObjectType($var->class->toString(), $methodName !== 'one');
} else if ($var instanceof Variable) {
/**
* @example :
* $query = MyModel::findBySql("");
* $query->all();
*/
// TODO $calledOnType = new ActiveQueryObjectType('??????????', $methodName === 'one');
} else {
throw new ShouldNotHappenException(sprintf('Unable to find ActiveRecord type for %s during method call %s at line %d', \get_class($calledOnType), $methodReflection->getName(), $methodCall->getLine()));
}
}
if ($methodName === 'asArray') {
$argType = isset($methodCall->args[0]) ? $scope->getType($methodCall->args[0]->value) : new ConstantBooleanType(true);
if (!$argType instanceof ConstantBooleanType) {
throw new ShouldNotHappenException(sprintf('Invalid argument provided to asArray method at line %d', $methodCall->getLine()));
}
return new ActiveQueryObjectType($calledOnType->getModelClass(), $argType->getValue());
}
if (!\in_array($methodName, ['one', 'all'], true)) {
return new ActiveQueryObjectType($calledOnType->getModelClass(), $calledOnType->isAsArray());
}
if ($methodName === 'one') {
return TypeCombinator::union(
new NullType(),
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ObjectType($calledOnType->getModelClass())
);
}
return new ArrayType(
new IntegerType(),
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ObjectType($calledOnType->getModelClass())
);
}
I solved like this
/** @var null $query */
$query = Transfer::find()
/** @var Transfer $transfer */
$transfer = $query->one(); // @phpstan-ignore-line
@marmichalski Hey! Are you repeating this error? A simple example that shows the current problem and is very annoying:
<?php
namespace myNameSpace;
use yii\db\ActiveQuery;
class MyQuery extends ActiveQuery
{
public function test(array $ids): ActiveQuery
{
return $this->andWhere(['not in', 'id', $ids]);
}
}
------ ----------------------------------------------------------------------------------------------
Line MyQuery.php
------ ----------------------------------------------------------------------------------------------
Internal error: Unexpected type PHPStan\Type\ThisType during method call andWhere at line 19
I tried changing the validation myself, but my current knowledge is a bit lacking. I will try to figure it out further, but maybe you have a solution to this problem?
I am trying this out and also getting this error a lot:
Internal error: Unexpected type PHPStan\Type\ThisType during method call andWhere...
Anybody have a solution?
I solved like this
/** @var null $query */ $query = Transfer::find() /** @var Transfer $transfer */ $transfer = $query->one(); // @phpstan-ignore-line
No, you did not solve, you put the issue under the carpet.