Make PHPStan understand Laravel Eloquent Builder query() - php

I am having a hard time making larastan / phpstan understand that query() should be based on Company model and not Eloquent\Model. What am I missing?
<?php
namespace App\Repositories;
use App\Models\Company;
/**
* #extends AbstractBaseRepository<Company>
*/
class CompanyRepository extends AbstractBaseRepository
{
public function __construct()
{
parent::__construct(new Company());
}
public function firstByDomain(string $domain): ?Company
{
return $this->query()
->where('domain', $domain)
->first();
}
}
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* #template TModel of Model
*/
abstract class AbstractBaseRepository
{
/** #var TModel */
protected $model;
/** #param TModel $model */
public function __construct(Model $model)
{
$this->model = $model;
}
public function query(): Builder
{
return $this->model->query();
}
}
And this is causing this error:
Method App\Repositories\CompanyRepository::firstByDomain() should return App\Models\Company|null but returns Illuminate\Database\Eloquent\Model|null.
It seems to me that this is caused by the query() method, returning an Eloquent Builder for Illuminate\Database\Eloquent\Model where I believe it should return an Eloquent Builder for App\Models\Company here.

You need to change the query method in AbstractBaseRepository to something like this:
/** #return Builder<TModel> */
public function query(): Builder
{
return $this->model->query();
}
because Builder class is also generic. Also PHPStan does not check the function/method bodies. So your return type needs to be accurate.

Related

Laravel Trait with constructor works in controller but not in model

I have a StripeClient service provider which needs a key to instantiate:-
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Stripe\StripeClient;
class StripeServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton(StripeClient::class, function ($app) {
return new StripeClient(config('services.stripe.secret'));
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return [StripeClient::class];
}
Then a trait with a bunch of api call functions like this:-
trait StripeClientTrait
{
protected $stripe;
function __construct(StripeClient $stripeClient)
{
$this->stripe = $stripeClient;
}
/**
* #param User $user
*
* #return \Stripe\Customer
* #throws \Stripe\Exception\ApiErrorException
*/
function createCustomer(User $user)
{
return $this->stripe->customers->create([ 'name' => $user->fullname,
'email' => $user->email
]);
}
...
The trait works in a controller perfectly as expected:-
class SubscriptionContoller extends Controller
{
use StripeClientTrait;
public function checkout()
{
try {
$customer = $this->createCustomer(Auth::user());
if($checkoutSession = $this->createCheckoutSession($customer)) {
return redirect($checkoutSession->url);
}
} catch (ApiErrorException $ex){
Log::error($ex->getMessage());
return back()->with(['error'=>$ex->getMessage()]);
}
return back();
}
...
But I now need to use the trait in a model to provide access to some api functions.
class Company extends Tenant
{
use HasFactory, StripeClientTrait;
but adding the trait causes:-
Too few arguments to function App\Models\Company::__construct(), 0 passed in /home/vagrant/code/profiler/vendor/spatie/laravel-multitenancy/src/Models/Concerns/UsesTenantModel.php on line 13 and exactly 1 expected
Can anyone tell me how to implement the trait without using the constructor? I just need some static function helpers to lookup stuff on the API.
Thanks for any guidance :-)
having persevered I've found this way to use the service container in a model:-
public function getPrices()
{
$stripe = app(StripeClient::class);
return $stripe->prices->all(['active'=>true]);
}
But would still like to understand how to use the trait in the model, if anyone could explain I'd be grateful

delete with eager load in laravel

I'm trying to delete a collection but I want to delete the NFTs related to a collection as well. How do I do this?
‍‍This is my Nft model Nft.php
class Nft extends Model
{
use HasFactory,SoftDeletes;
public function collection()
{
return $this->belongsTo(Collection::class);
}
}
This is my collection model Collection.php
class Collection extends Model
{
use HasFactory, SoftDeletes;
public function nfts()
{
return $this->hasMany(Nft::class);
}
}
This is my COntroller CollectionController.php
public function deleteCollection(int $id):RedirectResponse
{
Collection::with('nfts')->find($id)->delete();
return redirect('/collections');
}
But it did not work!!!
pleas help me!!!!!
At user model:
public function collection() {
return $this->belongsTo(Collection::class)->withTrashed(); }
Keep in mind that the observer events are not fired if you use mass delete for example. In the case Collection::with('nfts')->delete(); it might not work.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Collection extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleting(function ($collection) {
$collection->nfts()->delete()
});
}
}

Laravel 5.0 model test Mockery unable to mock method/static method does not exist?

I am using Laravel 5.0 to create phpunit test alongside the actual model.
I get errors in phpunit tests but no errors when controller calls the model and it returned the desired data.
sample.php
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class sample extends Model {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'sample';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['id','username','details','image'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public static function test()
{
return "Returned Text.";
}
public static function gettest()
{
return self::test();
}
public static function getItem()
{
return self::orderBy('username','asc')->get();
}
public static function findItem($id)
{
return self::find($id);
}
}
SampleTest.php
<?php namespace App;
use Mockery as m;
class SampleTest extends \PHPUnit_Framework_TestCase {
protected function setUp()
{
$this->mock = m::mock('App\sample')->makePartial();
}
protected function tearDown()
{
m::close();
}
/** #test */
public function should_return_string()
{
$response = $this->mock->test();
var_dump("test() returns :".$response);
}
/** #test */
public function should_return_string_from_test_function()
{
$response = $this->mock->gettest();
var_dump("gettest() returns :".$response);
}
/** #test */
public function should_return_mocked_data()
{
$this->mock->shouldReceive('test')->andReturn('Return Mocked Data');
$response = $this->mock->gettest();
var_dump("gettest() returns :".$response);
}
/** #test */
public function should_return_some_data_using_this_mock()
{
$this->mock->shouldReceive('get')->andReturn('hello');
$response = $this->mock->getItem();
}
}
Problem
When I use controller to call the model, it returned the desired data.
When I run phpunit on command prompt:-
test function is not mocked properly as it still returns the original string
getItem and findItem functions return an error saying
1) App\SampleTest::should_return_some_data_using_this_mock
BadMethodCallException: Static method Mockery_0_App_sample::getItem()
does not exist on this mock object
Question
How can I mock the function properly? Why it is saying the error code as shown above? Where was I doing it wrong?
Any help will be much appreciated.
Note: Test assertions is removed and replaced with var_dump to see the output on the command prompt.

Repositories Not be Instantiated

I'm trying to find out why I'm receiving this error. I'm following along. However the only difference is that at the time of the recording it was done with Laravel 4.25 and I am now using Laravel 5.0.
Repositories and Inheritance
BindingResolutionException in Container.php line 785:
Target [App\Repositories\User\UserRepository] is not instantiable.
<?php
namespace App\Http\Controllers;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Repositories\User\UserRepository;
use Illuminate\Http\Request;
class UsersController extends Controller {
private $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index() {
$users = $this->userRepository->getAll();
return $users;
}
}
<?php
namespace App\Repositories\User;
use App\Repositories\EloquentRepository;
class EloquentUserRepository extends EloquentRepository implements UserRepository
{
private $model;
function __construct(User $model) {
$this->model = $model;
}
}
<?php
namespace App\Repositories\User;
interface UserRepository {
public function getAll();
}
<?php
namespace App\Repositories;
abstract class EloquentRepository {
public function getAll() {
return $this->model->all();
}
public function getById() {
return $this->model->findOrFail($id);
}
}
You are type hinting an interface, and not the class itself. This error is occurring because Laravel cannot bind an interface because the binding must be instantiable. Abstract classes or interfaces are not valid unless Laravel knows the concrete (instantiable) class to substitute in for the abstract class / interface.
You will need to bind the EloquentUserRepository to the interface:
App::bind('UserRepository', 'EloquentUserRepository');

Dependency inversion issue in php. (Repository Pattern)

I am implementing a repository pattern in Laravel, and it seems to be very tedious. For example, let's say I have products then I have to create a ProductRepository interface then a ProductRepository class that implements that interface, now I have some very generic methods on the ProductRepository like:
retrieveAll
store
update
delete
And now I have to do the same thing for ingredients. It would be nice if I could simply create a ModelRepository interface with all those generic methods and implement it by passing a generic data type (namely the model), something similar to Java Generics:
<?php
interface ModelRepositoryInterface<T> {
function retrieveAll(): Collection<T>;
function store(T $item);
function update(int $id, T $data);
function delete(int $id);
}
But since php doesn't support generics how can I achieve this simplicity?
You can create a RepositoryServiceProvider to bind your repository interfaces to actual classes.
You can create a abstract Repository class with retrieveAll, store, update, delete and extend your Repositories and implement the interface. I have included below example with magic functions to be able to eloquent methods if I don't have any customization.
The below is not tested but its just to get the idea.
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
abstract class AbstractRepository implements RepositoryInterface
{
/**
* #var Builder|Model
*/
protected $model;
/**
* #return mixed
*/
public function getModel()
{
return $this->model;
}
/**
* #param array $columns
* #return \Illuminate\Database\Eloquent\Collection|Model[]
*/
public function all($columns = ['*'])
{
return $this->model->all($columns);
}
/**
* #param $name
* #param $arguments
* #return mixed
*/
public function __call($name, $arguments)
{
return $this->model->{$name}($arguments);
}
}
OrderRepository
<?php
namespace App\Repositories;
use App\Models\Order;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class OrderRepository extends AbstractRepository implements OrderRepositoryInterface
{
/**
* OrderRepository constructor.
* #param Order $model
*/
public function __construct(Order $model)
{
$this->model = $model;
}
public function countPaid(): int
{
return $this->model->paid()->count();
}
/**
* #return int
*/
public function countReady(): int
{
return $this->model->ready()->count();
}
/**
* #return int
*/
public function countCancelled(): int
{
return $this->model->cancelled()->count();
}
}
OrderRepositoryInterface
<?php
namespace App\Repositories;
interface OrderRepositoryInterface
{
}
RepositoryServiceProvider
<?php
namespace App\Providers;
use App\Repositories\OrderRepository;
use App\Repositories\OrderRepositoryInterface;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(OrderRepositoryInterface::class, OrderRepository::class);
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
RepositoryInterface
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface RepositoryInterface
{
function retrieveAll(): Collection;
function store(Model $item);
function update(int $id, Model $data);
function delete(int $id);
}

Categories