php - Using the Repository Pattern (and Query Scopes) with Relations -



php - Using the Repository Pattern (and Query Scopes) with Relations -

in laravel 4, query scopes available on queries (including ones generated relations queries). means next (example) models:

customer.php:

<?php class client extends eloquent { public function order() { homecoming $this->hasmany('order'); } }

order.php:

<?php class order extends eloquent { public function scopedelivered($query) { homecoming $query->where('delivered', '=', true); } public function customer() { homecoming $this->belongsto('customer'); } }

both of next work:

var_dump(order::delivered()->get()); // delivered orders var_dump(customer::find(1)->orders()->delivered()->get()); // orders client #1 delivered

this useful within controller because query logic finding delivered orders doesn't have repeated.

recently, though, i've been convinced repository pattern optimal not separation of concerns possibility of orm/db switch or necessity of adding middleware cache. repositories sense natural, because instead of having scopes bloat models, associated queries instead part of repository (which makes more sense because naturally method of collection not item).

for example,

<?php class eloquentorderrepository { protected $order; public function __construct(order $order) { $this->order = $order; } public function find($id) { /* ... */ } /* etc... */ public function alldelievered() { homecoming $this->order->where('delivered', '=', true)->get(); } }

however, have delivered scope repeated, avoid violating dry, remove model (which seems logical per justification above). now, can no longer can utilize scopes on relations (like $customer->orders()->delivered()). workaround here see somehow instantiating repository pre-made query (similar passed scopes in models) in relation base of operations class. involves changing (and overriding) lot of code , default behavior , seems create things more coupled should be.

given dilemma, misuse of repository? if not, solution way regain functionality like? or having scopes in models not tight plenty coupling justify code? if scopes aren't tight coupling, there way utilize both repository pattern , scopes while still beingness dry?

note: aware of some similar questions on similar topics none of them address issue presented here queries generated relationships, not rely on repository.

i've managed find solution. it's rather hacky , i'm not sure whether consider acceptable (it uses lot of things in ways weren't meant used). summarize, solution allows move scopes repository. each repository (on instantiation) booted once, , during process of scope methods extracted , added each query created eloquent model (via macros) way of illuminate\database\eloquent\scopeinterface.

the (hack-y) solution repository pattern implementation

app/lib/phpmycoder/repository/repository.php:

<?php namespace phpmycoder\repository; interface repository { public function all(); public function find($id); }

app/lib/phpmycoder/repository/order/orderrepository.php:

<?php namespace phpmycoder\repository\order; interface orderrepository extends phpmycoder\repository\repository {} adding eloquent repositories (and hack)

app/lib/phpmycoder/repository/order/eloquentorderrepository.php:

<?php namespace phpmycoder\repository\order; utilize phpmycoder\repository\eloquentbaserepository; class eloquentorderrepository extends eloquentbaserepository implements orderrepository { public function __construct(\order $model) { parent::__construct($model); } public function finished() { homecoming $this->model->finished()->get(); } public function scopefinished($query) { homecoming $query->where('finished', '=', true); } }

notice how repository contains scope stored in order model class. in database (for example), order needs have boolean column finished. we'll cover details of eloquentbaserepository below.

app/lib/phpmycoder/repository/eloquentbaserepository.php:

<?php namespace phpmycoder\repository; utilize illuminate\database\eloquent\model; abstract class eloquentbaserepository implements repository { protected $model; // stores repositories have been booted protected static $booted = array(); public function __construct(model $model) { $this->model = $model; $this->bootifnotbooted(); } protected function bootifnotbooted() { // boot 1 time per repository class, because need // add together scopes model 1 time if(!isset(static::$booted[get_class($this)])) { static::$booted[get_class($this)] = true; $this->boot(); } } protected function boot() { $modelscope = new modelscope(); // covered below $selfreflection = new \reflectionobject($this); foreach (get_class_methods($this) $method) { // find scope methods in repository class if (preg_match('/^scope(.+)$/', $method, $matches)) { $scopename = lcfirst($matches[1]); // closure scope method $scopemethod = $selfreflection->getmethod($method)->getclosure($this)->bindto(null); $modelscope->addscope($scopename, $scopemethod); } } // attach our special modelscope model class call_user_func([get_class($this->model), 'addglobalscope'], $modelscope); } public function __call($method, $arguments) { // handle calls scopes on repository // how handled on eloquent models if(method_exists($this, 'scope' . ucfirst($method))) { homecoming call_user_func_array([$this->model, $method], $arguments)->get(); } } /* phpmycoder\repository\order\orderrepository (inherited phpmycoder\repository\repository) */ public function all() { homecoming $this->model->all(); } public function find($id) { homecoming $this->model->find($id); } }

each time instance of repository class instantiated first time, boot repository. involves aggregating "scope" methods on repository modelscope object , applying model. modelscope apply our scopes each query created model (as seen below).

app/lib/phpmycoder/repository/modelscope.php:

<?php namespace phpmycoder\repository; utilize illuminate\database\eloquent\scopeinterface; utilize illuminate\database\eloquent\builder; class modelscope implements scopeinterface { protected $scopes = array(); // scopes need apply each query public function apply(builder $builder) { foreach($this->scopes $name => $scope) { // add together scope builder macro (hack-y) // mimics behavior , homecoming value of builder::callscope() $builder->macro($name, function() use($builder, $scope) { $arguments = func_get_args(); array_unshift($arguments, $builder->getquery()); homecoming call_user_func_array($scope, $arguments) ?: $builder->getquery(); }); } } public function remove(builder $builder) { // removing not possible (no builder::removemacro), // we'll overwrite method 1 throws // badmethodcallexception foreach($this->scopes $name => $scope) { $builder->macro($name, function() use($name) { $classname = get_class($this); throw new \badmethodcallexception("call undefined method {$classname}::{$name}()"); }); } } public function addscope($name, \closure $scope) { $this->scopes[$name] = $scope; } } the serviceprovider , composer file

app/lib/phpmycoder/repository/repositoryserviceprovider.php:

<?php namespace phpmycoder\repository; utilize illuminate\support\serviceprovider; utilize phpmycoder\repository\order\eloquentorderrepository; class repositoryserviceprovider extends serviceprovider { public function register() { // bind repository interface eloquent repository class $this->app->bind('phpmycoder\repository\order\orderrepository', function() { homecoming new eloquentorderrepository(new \order); }); } }

be sure add together service provider providers array in app.php config:

'phpmycoder\repository\repositoryserviceprovider',

and add together app/lib composer's autoload

"autoload": { "psr-0": { "phpmycoder\\": "app/lib" }, /* etc... */ },

this require composer.phar dump-autoload.

the models

app/models/customer.php:

<?php class client extends eloquent { public function orders() { homecoming $this->hasmany('order'); } }

notice brevity, i've excluded writing repository customer, in real application should.

app/model/order.php:

<?php class order extends eloquent { public function customer() { homecoming $this->belongsto('customer'); } }

notice how scope not longer stored in order model. makes more structural sense, because collection level (repository) should responsible scopes applying orders while order should concerned details specific 1 order. demo work, order must have integer foreign key customer_id customers.id , boolean flag finished.

usage in controller

app/controllers/ordercontroller.php:

<?php // ioc handle passing our controller proper instance utilize phpmycoder\repository\order\orderrepository; class ordercontroller extends basecontroller { protected $orderrepository; public function __construct(orderrepository $orderrepository) { $this->orderrepository = $orderrepository; } public function test() { $allorders = $this->orderrepository->all(); // our repository can handle scope calls how // eloquent models handle them $finishedorders = $this->orderrepository->finished(); // if had made one, instead utilize client repository // notice though how relation query has order scopes $finishedordersforcustomer = customer::find(1)->orders()->finished(); } }

our repository not contains scopes kid model, more solid. come ability handle calls scope real eloquent model would. , add together scopes each query created model have access them when retrieving related models.

problems approach a lot of code little functionality: arguably much accomplish desired result it's hacky: macros on illuminate\database\eloquent\builder , illuminate\database\eloquent\scopeinterface (in conjunction illuminate\database\eloquent\model::addglobalscope) used in ways weren't intended be it requires instantiation of repository (major issue): if you're within customercontroller , have instantiated customerrepository, $this->customerrepository->find(1)->orders()->finished()->get() won't work expected (the finished() macro/scope won't added each order query unless instantiate orderrepository).

i'll investigate if there more elegant solution (which remedies issues listed above), best solution can find far.

related resources on repository pattern creating flexible controllers in laravel 4 using repositories eloquent tricks improve repositories

php laravel orm laravel-4 repository-pattern

Comments

Popular posts from this blog

php - Android app custom user registration and login with cookie using facebook sdk -

django - Access session in user model .save() -

php - .htaccess Multiple Rewrite Rules / Prioritizing -