Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure firstOrNew/Create can be called without parameters in Laravel 8+ #157

Merged
merged 4 commits into from
Jul 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions stubs/6/EloquentBuilder.stubphp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
caugner marked this conversation as resolved.
Show resolved Hide resolved

namespace Illuminate\Database\Eloquent;
/**
* @template-covariant TModel of \Illuminate\Database\Eloquent\Model
* @property-read HigherOrderBuilderProxy $orWhere
*
* @mixin \Illuminate\Database\Query\Builder
*/
class Builder
{
/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrNew(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrCreate(array $attributes, array $values = []) { }
}
27 changes: 27 additions & 0 deletions stubs/6/HasOneOrMany.stubphp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Illuminate\Database\Eloquent\Relations;

/**
* @template TRelatedModel of Model
* @template-extends Relation<TRelatedModel>
* @mixin \Illuminate\Database\Eloquent\Builder<TRelatedModel>
*/
abstract class HasOneOrMany extends Relation
{
/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrNew(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrCreate(array $attributes, array $values = []) { }
}
26 changes: 26 additions & 0 deletions stubs/8/EloquentBuilder.stubphp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Illuminate\Database\Eloquent;

/**
* @template-covariant TModel of \Illuminate\Database\Eloquent\Model
* @property-read HigherOrderBuilderProxy $orWhere
*
* @mixin \Illuminate\Database\Query\Builder
*/
class Builder
{
/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrNew(array $attributes = [], array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrCreate(array $attributes = [], array $values = []) { }
}
30 changes: 30 additions & 0 deletions stubs/8/HasOneOrMany.stubphp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Illuminate\Database\Eloquent\Relations;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

/**
* @template TRelatedModel of Model
* @template-extends Relation<TRelatedModel>
* @mixin \Illuminate\Database\Eloquent\Builder<TRelatedModel>
*/
abstract class HasOneOrMany extends Relation
{
/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrNew(array $attributes = [], array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrCreate(array $attributes = [], array $values = []) { }
}
14 changes: 0 additions & 14 deletions stubs/EloquentBuilder.stubphp
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,6 @@ class Builder
*/
public function findOrNew($id, $columns = ['*']) { }

/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrNew(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return TModel
*/
public function firstOrCreate(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
Expand Down
17 changes: 0 additions & 17 deletions stubs/HasOneOrMany.stubphp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace Illuminate\Database\Eloquent\Relations;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;

/**
* @template TRelatedModel of Model
Expand Down Expand Up @@ -46,22 +45,6 @@ abstract class HasOneOrMany extends Relation
*/
public function findOrNew($id, $columns = ['*']) { }

/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrNew(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
* @return \Illuminate\Database\Eloquent\Model
* @psalm-return TRelatedModel
*/
public function firstOrCreate(array $attributes, array $values = []) { }

/**
* @param array $attributes
* @param array $values
Expand Down
130 changes: 95 additions & 35 deletions tests/acceptance/EloquentBuilderTypes.feature
Original file line number Diff line number Diff line change
Expand Up @@ -15,73 +15,78 @@ Feature: Eloquent Builder types
</plugins>
</psalm>
"""
And I have the following code preamble
"""
<?php declare(strict_types=1);
namespace Tests\Psalm\LaravelPlugin\Sandbox;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Tests\Psalm\LaravelPlugin\Models\User;
"""

Scenario: Models can call eloquent query builder instance methods
Given I have the following code
"""
<?php declare(strict_types=1);

use Tests\Psalm\LaravelPlugin\Models\User;

final class UserRepository
{

/**
* @return \Illuminate\Database\Eloquent\Builder<User>
* @return Builder<User>
*/
public function getNewQuery(): \Illuminate\Database\Eloquent\Builder
public function getNewQuery(): Builder
{
return User::query();
}

/**
* @return \Illuminate\Database\Eloquent\Builder<User>
* @return Builder<User>
*/
public function getNewModelQuery(): \Illuminate\Database\Eloquent\Builder
public function getNewModelQuery(): Builder
{
return (new User())->newModelQuery();
}

/**
* @param \Illuminate\Database\Eloquent\Builder<User> $builder
* @param Builder<User> $builder
*/
public function firstOrFailFromBuilderInstance(\Illuminate\Database\Eloquent\Builder $builder): User {
public function firstOrFailFromBuilderInstance(Builder $builder): User {
return $builder->firstOrFail();
}

/**
* @param \Illuminate\Database\Eloquent\Builder<User> $builder
* @param Builder<User> $builder
*/
public function findOrFailFromBuilderInstance(\Illuminate\Database\Eloquent\Builder $builder): User {
public function findOrFailFromBuilderInstance(Builder $builder): User {
return $builder->findOrFail(1);
}

/**
* @param \Illuminate\Database\Eloquent\Builder<User> $builder
* @return \Illuminate\Database\Eloquent\Collection<User>
* @param Builder<User> $builder
* @return Collection<User>
*/
public function findMultipleOrFailFromBuilderInstance(\Illuminate\Database\Eloquent\Builder $builder): \Illuminate\Database\Eloquent\Collection {
public function findMultipleOrFailFromBuilderInstance(Builder $builder): Collection {
return $builder->findOrFail([1, 2]);
}

/**
* @param \Illuminate\Database\Eloquent\Builder<User> $builder
* @param Builder<User> $builder
*/
public function findOne(\Illuminate\Database\Eloquent\Builder $builder): ?User {
public function findOne(Builder $builder): ?User {
return $builder->find(1);
}

/**
* @param \Illuminate\Database\Eloquent\Builder<User> $builder
* @param Builder<User> $builder
*/
public function findViaArray(\Illuminate\Database\Eloquent\Builder $builder): \Illuminate\Database\Eloquent\Collection {
public function findViaArray(Builder $builder): Collection {
return $builder->find([1]);
}

/**
* @return \Illuminate\Database\Eloquent\Builder<User>
* @return Builder<User>
*/
public function getWhereBuilderViaInstance(array $attributes): \Illuminate\Database\Eloquent\Builder {
public function getWhereBuilderViaInstance(array $attributes): Builder {
return (new User())->where($attributes);
}
}
Expand All @@ -90,10 +95,16 @@ Feature: Eloquent Builder types
Then I see no errors

Scenario: can call static methods on model
Given I have the following code
Given I have the following code preamble
"""
<?php declare(strict_types=1);

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
"""
And I have the following code
"""

final class User extends \Illuminate\Database\Eloquent\Model {
protected $table = 'users';
};
Expand All @@ -102,17 +113,17 @@ Feature: Eloquent Builder types
{

/**
* @return \Illuminate\Database\Eloquent\Builder<User>
* @return Builder<User>
*/
public function getWhereBuilderViaStatic(array $attributes): \Illuminate\Database\Eloquent\Builder
public function getWhereBuilderViaStatic(array $attributes): Builder
{
return User::where($attributes);
}

/**
* @psalm-return \Illuminate\Database\Eloquent\Collection<User>
* @psalm-return Collection<User>
*/
public function getWhereViaStatic(array $attributes): \Illuminate\Database\Eloquent\Collection
public function getWhereViaStatic(array $attributes): Collection
{
return User::where($attributes)->get();
}
Expand All @@ -122,20 +133,25 @@ Feature: Eloquent Builder types
Then I see no errors

Scenario:
Given I have the following code
Given I have the following code preamble
"""
<?php declare(strict_types=1);

final class User extends \Illuminate\Database\Eloquent\Model {
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
"""
And I have the following code
"""
final class User extends Model {
protected $table = 'users';
};

final class UserRepository
{
/**
* @return \Illuminate\Database\Eloquent\Builder<User>
* @return Builder<User>
*/
public function test_failure(): \Illuminate\Database\Eloquent\Builder
public function test_failure(): Builder
{
return User::fakeQueryMethodThatDoesntExist();
}
Expand All @@ -150,11 +166,6 @@ Feature: Eloquent Builder types
Scenario: can call methods on underlying query builder
Given I have the following code
"""
<?php declare(strict_types=1);

use Tests\Psalm\LaravelPlugin\Models\User;
use \Illuminate\Database\Eloquent\Builder;

/**
* @psalm-param Builder<User> $builder
* @psalm-return Builder<User>
Expand All @@ -165,3 +176,52 @@ Feature: Eloquent Builder types
"""
When I run Psalm
Then I see no errors

Scenario: cannot call firstOrNew and firstOrCreate without parameters in Laravel 6.x
Given I have the "laravel/framework" package satisfying the "6.*"
And I have the following code
"""
/**
* @psalm-param Builder<User> $builder
* @psalm-return User
*/
function test_firstOrCreate(Builder $builder): User {
return $builder->firstOrCreate();
}

/**
* @psalm-param Builder<User> $builder
* @psalm-return User
*/
function test_firstOrNew(Builder $builder): User {
return $builder->firstOrNew();
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Builder::firstorcreate saw 0 |
| TooFewArguments | Too few arguments for method Illuminate\Database\Eloquent\Builder::firstornew saw 0 |

Scenario: can call firstOrNew and firstOrCreate without parameters in Laravel 8.x
Given I have the "laravel/framework" package satisfying the ">= 8.0"
And I have the following code
"""
/**
* @psalm-param Builder<User> $builder
* @psalm-return User
*/
function test_firstOrCreate(Builder $builder): User {
return $builder->firstOrCreate();
}

/**
* @psalm-param Builder<User> $builder
* @psalm-return User
*/
function test_firstOrNew(Builder $builder): User {
return $builder->firstOrNew();
}
"""
When I run Psalm
Then I see no errors
Loading