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);
}
Related
I want to make a Collection macro function in the service provider.
The problem is after importing the Collection class i can not access the collection funtion $this->values()
But when I use the same code in the controller is working fine.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Collection;
class CollectionExtensions extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
}
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
Collection::macro('transpose', function () {
$items = array_map(function (...$items) {
return $items;
}, ...$this->values());
return new static($items);
});
}
}
I have the following single responsibility repository class
<?php
namespace App\Launches\Repositories\Customer;
use App\Launches\Models\Customer\Subscription;
use Illuminate\Support\Collection;
final class GetSubscriptionRepository
{
/**
* #param int $productId
* #return Collection
*/
public function byProductId(int $productId): Collection
{
return Subscription::where('product_id', $productId)->get();
}
}
I want to be able to test that method, to make sure the object retrieve is the one I've asked. So I've done the following:
<?php
namespace Tests\Unit\Launches\Repository\Customer;
use App\Launches\Models\Customer\Subscription;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Collection;
use Tests\TestCase;
use App\Launches\Repositories\Customer\GetSubscriptionRepository;
class GetSubscriptionRepositoryTest extends TestCase
{
use RefreshDatabase;
/**
* #var GetSubscriptionRepository
*/
protected $getSubscriptionRepository;
/**
* #var Collection
*/
protected $subscriptionCollection;
public function setUp(): void
{
parent::setUp();
$this->getSubscriptionRepository = new GetSubscriptionRepository();
$this->subscriptionCollection = factory(Subscription::class, 20)->make();
}
/**
* #covers GetSubscriptionRepository::byProductId
*/
public function testSubscriptionIsPickedById()
{
$randomSubscription = $this->subscriptionCollection->random(1)->first()->toArray();
$pickedSubscription = $this->getSubscriptionRepository->byProductId($randomSubscription['id']);
$this->assertEquals($randomSubscription['id'], $pickedSubscription->get('id'));
}
}
It's always returning null on $this->getSubscriptionRepository->byProductId($randomSubscription['id']);
It is obviously expecting stuff to be in the database, is there a way to test it without adding stuff to the database?
This is how I create helper (App\Helpers\Settings.php)
namespace App\Helpers;
use Illuminate\Database\Eloquent\Model;
class Settings {
protected $settings = [];
public function __construct() {
$this->settings['AppName'] = 'Test';
}
/**
* Fetch all values
*
* #return mixed
*/
public function getAll () {
return $this->settings;
}
}
Creating facade (App\Helpers\Facades\SettingsFacade.php)
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Settings extends Facade {
protected static function getFacadeAccessor() {
return 'Settings';
}
}
Creating Service Provider (App\Providers\SettingsServiceProvider.php)
namespace App\Providers;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
class SettingsServiceProvider extends ServiceProvider {
/**
* Bootstrap the application events.
*
* #return void
*/
public function boot() {
}
/**
* Register the service provider.
*
* #return void
*/
public function register() {
App::bind( 'Settings', function () {
return new \App\Helpers\Settings;
});
} */
}
Registering provider (App\Providers\SettingsServiceProvider::class)
Creating alias: 'Settings' => App\Facades\Settings::class
Running composer dump-autoload
Trying to use facade Settings::getAll();
Getting error Class 'App\Http\Controllers\Settings' not found
Can’t figure out why I cannot create facade and getting that error
try this one.
App\Helpers\Settings.php
namespace App\Helpers;
use Illuminate\Database\Eloquent\Model;
class Settings {
protected $settings = [];
public function __construct() {
$this->settings['AppName'] = 'Test';
}
/**
* Fetch all values
*
* #return mixed
*/
public function getAll () {
return $this->settings;
}
}
App/Http/Controllers/XyzController.php
use Facades\App\Settings;
class XyzController extends Controller
{
public function showView()
{
return Settings::getAll();
}
}
web.php
Route::get('/','XyzController#showView');
use Facades\App\Helpers\Settings;
Route::get('/direct',function() {
return Settings::getAll();
});
use laravel Real time facades
I have made a header.blade.php which is extended to many View pages. in header menu items come from database.So I used ViewComposer.
Here is my ComposerServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
View::composer(['cart.layouts.header'], 'App\Http\ViewComposers\CategoryComposer#compose');
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
here is the CategoryComposer Class:
<?php
namespace App\Http\ViewComposers;
use Illuminate\View\View;
class CategoryComposer
{
public $categoryList = [];
/**
* Create a movie composer.
*
* #return void
*/
public function __construct()
{
$this->categoryList = \DB::table('categories')
->select('categories.*')
->get();
}
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view)
{
$view->with('categories', end($this->categoryList));
}
}
I have also registered the Service Provider in config/app
But the problem is I got error in other pages like Undefined variable: categories
Can you suggest me what could be the Error here?
I'm building an API and I would ask something about using namespaces on a Symfony2 controller.
Is there a real difference doing :
<?php
namespace My\APIBundle\Controller;
use FOS\RestBundle\Controller\Annotations\View,
FOS\RestBundle\Controller\Annotations\Post,
FOS\RestBundle\Controller\Annotations\Get;
use [...]
class MyRestController extends Controller {
[...]
/**
* #View()
* #Get()
*/
public function getAction(Thing $thing) {
return $thing;
}
/**
* #View()
* #Post()
*/
public function postAction() {
}
or doing
<?php
namespace My\APIBundle\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use [...]
class MyRestController extends Controller {
[...]
/**
* #Rest\View()
* #Rest\Get()
*/
public function getAction(Thing $thing) {
return $thing;
}
/**
* #Rest\View()
* #Rest\Post()
*/
public function postAction() {
}
Would the alias load everything in the given namespace, losing perfs ?
Or will it load only annoted classes, unitary ?