mirror of
https://github.com/danog/psalm-plugin-laravel.git
synced 2025-01-22 21:31:21 +01:00
Merge pull request #81 from psalm/polymorphic-support
feature: support for polymorphic relations
This commit is contained in:
commit
6d863cda1f
@ -2,11 +2,17 @@
|
||||
|
||||
namespace Psalm\LaravelPlugin\PropertyProvider;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use PhpParser;
|
||||
use Psalm\Context;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Type;
|
||||
use Psalm\StatementsSource;
|
||||
use function in_array;
|
||||
use function str_replace;
|
||||
|
||||
class ModelPropertyProvider implements
|
||||
@ -110,12 +116,20 @@ class ModelPropertyProvider implements
|
||||
if (!$methodReturnType) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
/** @var \Psalm\Type\Union|null $modelType */
|
||||
$modelType = null;
|
||||
/** @var \Psalm\Type\Atomic\TGenericObject|null $relationType */
|
||||
$relationType = null;
|
||||
|
||||
// In order to get the property value, we need to decipher the generic relation object
|
||||
foreach ($methodReturnType->getAtomicTypes() as $atomicType) {
|
||||
if (!$atomicType instanceof Type\Atomic\TGenericObject) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relationType = $atomicType;
|
||||
|
||||
foreach ($atomicType->getChildNodes() as $childNode) {
|
||||
if (!$childNode instanceof Type\Union) {
|
||||
continue;
|
||||
@ -124,12 +138,25 @@ class ModelPropertyProvider implements
|
||||
if (!$atomicType instanceof Type\Atomic\TNamedObject) {
|
||||
continue;
|
||||
}
|
||||
return $childNode;
|
||||
|
||||
$modelType = $childNode;
|
||||
break 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Type::getMixed();
|
||||
$returnType = $modelType;
|
||||
|
||||
// these methods return collection instances
|
||||
if ($modelType && $relationType && in_array($relationType->value, [BelongsToMany::class, HasMany::class, HasManyThrough::class, MorphMany::class])) {
|
||||
$returnType = new Type\Union([
|
||||
new Type\Atomic\TGenericObject(Collection::class, [
|
||||
$modelType
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return $returnType ?: Type::getMixed();
|
||||
}
|
||||
|
||||
if (self::accessorExists($codebase, $fq_classlike_name, $property_name)) {
|
||||
|
16
src/Stubs/MorphMany.stubphp
Normal file
16
src/Stubs/MorphMany.stubphp
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @template TRelatedModel of Model
|
||||
* @template-extends MorphOneOrMany<TRelatedModel>
|
||||
* @mixin \Illuminate\Database\Eloquent\Builder<TRelatedModel>
|
||||
*/
|
||||
class MorphMany extends MorphOneOrMany
|
||||
{
|
||||
|
||||
}
|
16
src/Stubs/MorphOneOrMany.stubphp
Normal file
16
src/Stubs/MorphOneOrMany.stubphp
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Illuminate\Database\Eloquent\Relations;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @template TRelatedModel of Model
|
||||
* @template-extends HasOneOrMany<TRelatedModel>
|
||||
* @mixin \Illuminate\Database\Eloquent\Builder<TRelatedModel>
|
||||
*/
|
||||
class MorphOneOrMany extends HasOneOrMany
|
||||
{
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ namespace Tests\Psalm\LaravelPlugin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
final class Comment extends Model
|
||||
{
|
||||
@ -14,4 +15,12 @@ final class Comment extends Model
|
||||
{
|
||||
return $this->belongsTo(Post::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the owning commentable model.
|
||||
*/
|
||||
public function commentable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
||||
|
18
tests/Models/Video.php
Normal file
18
tests/Models/Video.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Psalm\LaravelPlugin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
final class Video extends Model
|
||||
{
|
||||
/**
|
||||
* Get all of the video's comments.
|
||||
* @psalm-return MorphMany<Comment>
|
||||
*/
|
||||
public function comments(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Comment::class, 'commentable');
|
||||
}
|
||||
}
|
@ -22,12 +22,14 @@ Feature: Eloquent Relation Types
|
||||
|
||||
use \Illuminate\Database\Eloquent\Builder;
|
||||
use \Illuminate\Database\Eloquent\Model;
|
||||
use \Illuminate\Database\Eloquent\Collection;
|
||||
use \Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use \Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use \Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use \Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use \Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use \Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use \Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use \Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
use Tests\Psalm\LaravelPlugin\Models\Comment;
|
||||
@ -37,6 +39,7 @@ Feature: Eloquent Relation Types
|
||||
use Tests\Psalm\LaravelPlugin\Models\Post;
|
||||
use Tests\Psalm\LaravelPlugin\Models\Role;
|
||||
use Tests\Psalm\LaravelPlugin\Models\User;
|
||||
use Tests\Psalm\LaravelPlugin\Models\Video;
|
||||
"""
|
||||
|
||||
Scenario: Models can declare one to one relationships
|
||||
@ -160,6 +163,46 @@ Feature: Eloquent Relation Types
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Models can declare many to many polymorphic relationships
|
||||
Given I have the following code
|
||||
"""
|
||||
final class Repository
|
||||
{
|
||||
/**
|
||||
* @psalm-return MorphMany<Comment>
|
||||
*/
|
||||
public function getCommentsRelation(Video $video): MorphMany {
|
||||
return $video->comments();
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return Collection<Comment>
|
||||
*/
|
||||
public function getComments(Video $video): Collection {
|
||||
return $video->comments;
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Polymorphic models can retrieve their inverse relation
|
||||
Given I have the following code
|
||||
"""
|
||||
final class Repository
|
||||
{
|
||||
/**
|
||||
* todo: this should be a union of possible types...
|
||||
* @psalm-return mixed
|
||||
*/
|
||||
public function getCommentable(Comment $comment) {
|
||||
return $comment->commentable;
|
||||
}
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Relationships can be accessed via a property
|
||||
Given I have the following code
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user