andersao / l5-repository Goto Github PK
View Code? Open in Web Editor NEWLaravel 5 - Repositories to abstract the database layer
Home Page: http://andersao.github.io/l5-repository
License: MIT License
Laravel 5 - Repositories to abstract the database layer
Home Page: http://andersao.github.io/l5-repository
License: MIT License
In my view composer I have
class FrontComposer
{
protected $postRepo;
public function __construct(PostRepository $postRepo)
{
$this->postRepo = $postRepo;
}
public function compose($view)
{
$view->with([
'languages' => \LaravelLocalization::getSupportedLocales(),
'posts' => $this->postRepo->getPostsByLanguage(\LaravelLocalization::getCurrentLocale())
]);
}
}
My repository class looks like
model->findWhere(['lang'=>$lang]);
}
```
}
I currently have the line of code Role::whereName('Customer')->first()->users()->paginate(20);
Users and Roles are a many to many relationship. If both of these entities have the repositories created as per this package, how would I go about executing this query? I managed to get it to work with this $this->role->with(['users'])->findByField('name','Customer')->first()->users()->paginate(20);
, however I'm not posiive this is the most effiecient (db call) way to write it
Hi,
I use your package and for the Role
model I have a RoleRepository
. I have an error if I implements cache in my app.
<?php
namespace App\Models;
use Dimsav\Translatable\Translatable;
use Prettus\Repository\Contracts\CacheableInterface;
use Prettus\Repository\Traits\CacheableRepository;
use Zizaco\Entrust\EntrustRole;
class Role extends EntrustRole implements CacheableInterface
{
use CacheableRepository, Translatable;
/**
* @var array
*/
protected $translatedAttributes = ['display_name', 'description'];
/**
* @var array
*/
protected $fillable = ['name'];
/**
* @var array
*/
protected $with = ['translations'];
}
Error:
FatalErrorException in Role.php line 28:
Cannot make static method Illuminate\Database\Eloquent\Model::all() non static in class Prettus\Repository\Traits\CacheableRepository
It's an incompatibility with L 5.1.x ?
I'm trying to do something along these lines
$this->order->with(['orderedBy', 'payment', 'assignedTo', 'orderStatus', 'lineItems.product', 'lineItems.orderLineItemStatus','domain'])->paginate(25);
Before implementing this package (I forget exactly) the page had <15 queries executed. Now it has over 100. Any idea how I can accomplish this? `$this->order is an OrderRepository that uses this package.
Eg: for simple scenario like Category has multiple products. When select all category with products how to pass two Models to transformers. Its take only one model. How we can implement this?
I wonder how would one be confident using this package without some concrete tests. You guys should think about it!!
I'm trying to create a page which list items from newest to old.
I can easily do this by doing this in my repository's boot method: $this->pushCriteria(app('Prettus\Repository\Criteria\RequestCriteria'));
and adding ?orderBy=id&sortedBy=desc.
But I want it to do this by default.
So I push a custom criteria in my repository's boot method, but I end up getting "Undefined variable: query".
I have followed the documentation, but after re-checking etc. I still get the "undefined variable query"-message
What am I doing wrong?
What's the easiest way using this package - while keeping the repositories structure in mind - to validate ownership of models? Without repositories I would add a new function to the model, something like below but I can't figure out what would be the most efficient reusable DRY construction for this structure.
//Game.php
public function verifyOwnership(User $user)
{
if ($this->user_id != $user->id){
throw new Exception();
}
return true;
}
//GamesController.php
public function show($id)
{
try
{
$game = Game::find($id);
$game->verifyOwnership(Auth::user());
//more logic
} catch (Exception $e) {
throw $e;
}
}
How about a layer of caching ? Of course configurable to enable for all/only few, skip, on-demand with default time and specific to each?
Would make this package perfect in all around.
I was thinking last night about the namespace for the generators.
Since you did an awesome job with this package and I want to contribute as much as I can.
Yes we have the commands to generate the repositories, the model and the presenter.
Another cool thing would be to generate the provider too that binds the contract to the implementation in the container. So the user has to include it among the apps providers. Maybe we could append directly the created service provider to the providers array in app.php?
Another thing could be generating the controller also for the given resource. With the repository injected. And all the methods set up already.
What about routes? We could append the newly created resource to the routes file too?
As a developer I tend to try and write the least boilerplate I can during development and concentrate on the business logic and all that stuff. Making a single command that generates everything and sets it up for me would be great.
So to recap, the flow would be like this:
Namespacing the commands, for example, under 'prettus', we could do prettus:provider, prettus:controller without conflicting with existing Laravel commands.
What do you think about this?
I have been installed this package on laravel 5.0 and I follow your instructions in readme.md precisely..
But when I run my app I always get
App\UnivRepository does not exist..
Is there any mistake that I miss..
Or this package doesn't work on laravel 5..
This is awesome. This repository has saved my lot of time. I have used your repo in my codebase. I am finding solution for the following issue.
Here is the url: http://prettus.local/posts?filter=id;title and I am also using PostTransformer(https://github.com/andersao/l5-repository/#fractal-presenter) and have passed Post class as an argument. Now, I am only looking for two columns i.e. Id and Title where as PostTransformer is returning me array with all the columns that are there in the transform method i.e. Id, Title and Content. Content is always Null.
Any solution? Thanks.
To reproduce the bug, call two times a public method using the same repository instance. Example:
$repo = app('SomeRepo');
$item = $repo->findWhere(['id' => 1]); // This returns the item
$item = $repo->findWhere(['id' => 2]); // This returns nothing!
The second call is equivalent to:
Model::where('id, '=', 1)->where('id', '=', 2)->get();
Which returns nothing of course.
To fix that, execute $this->makeModel()
inside applyCriteria()
.
Btw it would be very nice if you would add some tests. Laracasts integrated would help a lot.
When adding
app->register(Prettus\Repository\Providers\RepositoryServiceProvider::class);
I get:
lumen.ERROR: exception 'ErrorException' with message 'Declaration of Prettus\Repository\Providers\EventServiceProvider::boot() should be compatible with Illuminate\Foundation\Support\Providers\EventServiceProvider::boot()' in /var/www/www/vendor/prettus/l5-repository/src/Prettus/Repository/Providers/EventServiceProvider.php:6
from my composer json
"require": {
"ext-mysql": "*",
"ext-memcached": "*",
"laravel/lumen-framework": "5.*",
"vlucas/phpdotenv": "~1.0",
"prettus/l5-repository": "dev-master"
}
Hello,
I have noticed that you are using the findOrFail method for the following methods : update or delete.
But what if the app retrieves the model with an other attribute than the ID ?
Example :
The user request a blog article. The url is http://myblog.com/posts/the-prettus-repository.
The route parameter is not an ID but a slug. Since the repository accepts only an ID, we should find the model by its slug attribute and then pass the model's ID to the update / delete method. And then, the repository makes another query to retrieve (again) the model from the database.
So we perform two queries for a simple update or delete, when we can perform only one query. If there is any FormRequest with a unique check involved, it makes three requests for the same model.
What about :
Thanks,
As it stands now, generators provide little flexibility in how we store our generated classes within our application. We, for instance, store our models in Models
directory, our repository interfaces under Contracts
and our repositories under Repositories
. It would also be nice to provide us a way to specify where transformations and presenters were stored as well.
This could easily be accomplished through configuration. Perhaps something like this...
'paths' => [
'models' => 'Models',
'repositories' => 'Repositories/Eloquent',
'interfaces' => 'Contracts/Repositories',
'transformers' => 'Transformers',
'presenters' => 'Presenters'
]
... along with some slight modification made to the stubs to accommodate the use
paths.
I'm trying to use your repository in Lumen, even though it's meant for Laravel 5.
It was pretty straight forward.
You have to manually create a config directory and copy the repository config into it.
And then you shouldn't register the service provider.
But, when I'm trying to use rules within my repository, I get:
BindingResolutionException:
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
I don't know what that exactly means and why the TranslatorInterface is needed.
But I more or less randomly solved it by following this answer: http://stackoverflow.com/a/20279179.
Which led me to adding this in my AppServiceProvider:
$this->app->bind('Symfony\Component\Translation\TranslatorInterface', function($app)
{
return $app['translator'];
});
Now it all seems to work.
It seems to me that 95% of the time there is a set of fields on (nearly) every model that can be safely put in the protected $hidden
array. The things on the front of my mind are fields like created_at, updated_at, deleted_at, created_by, deleted_by, updated_by
.
I found this thread discussing this idea for a more specific implementation.
I think it would be a big value add to be able to set on each model a set of fields to hide on default that could also be overridable. Similar to how withTrashed()
works in Eloquent
I have implemented a lists method to retrieve data for select tag.
namespace App\Repositories\Eloquent;
use Prettus\Repository\Eloquent\BaseRepository;
use Prettus\Repository\Criteria\RequestCriteria;
use App\Contracts\Repositories\CountryRepository;
use App\Models\Country;
/**
* Class UserRepositoryEloquent
* @package namespace App\Repositories\Eloquent;
*/
class CountryRepositoryEloquent extends BaseRepository implements CountryRepository
{
/**
* Specify Model class name
*
* @return string
*/
public function model()
{
return Country::class;
}
/**
* Boot up the repository, pushing criteria
*/
public function boot()
{
$this->pushCriteria(app(RequestCriteria::class));
}
public function lists($column)
{
return $this->model->get()->lists($column);
}
}
The get()
method before lists()
method is used to retrieve the translation: laravel-translatable
namespace App\Repositories\Eloquent\Criterias\Country;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
class HasTranslation implements CriteriaInterface
{
/**
* Apply criteria in query repository
*
* @param $model
* @param RepositoryInterface $repository
*
* @return mixed
*/
public function apply( $model, RepositoryInterface $repository )
{
$model = $model->where('has_translation', 1);
return $model;
}}
When I use it in my controller:
namespace App\Http\Controllers;
use App\Contracts\Repositories\CountryRepository;
use App\Repositories\Eloquent\Criterias\Country\HasTranslation;
class ProfileController extends Controller {
/**
* @var CountryRepository
*/
private $language;
/**
* @param CountryRepository $language
*/
public function __construct( CountryRepository $language )
{
$this->language = $language;
}
/**
* @return \Illuminate\View\View
*/
public function index()
{
return view( 'profile.index' )->with( [
'user' => auth()->user(),
'language' => $this->language->getByCriteria( new HasTranslation() )->lists( 'lang' )
] );
}
}
This dump return array(1 => 'Français', 2 => 'Espagnol')
\App\Models\Country::where('has_translation', 1)->get()->lists('lang', 'id')->toArray();
This dump return array(0 => 'Français', 1 => 'Espagnol')
$this->language->getByCriteria( new HasTranslation() )->lists( 'lang' )->all();
I don't know why the repository return an array with the first index to 0 and not the model id.
Hi, I love using your repository and am learning its nuances. I'm curious how you could draw this statement utilizing the repository:
$users = User::with(['posts' => function($query)
{
$query->where('title', 'like', '%first%');
}])->get();
This example is drawn from the laravel docs. I'm looking at an entity with many relationships, and may need to filter from any given one.
Thank you
I need to bring data from two tables and I need the data come in alphabetical order, but I could only put in the first table, the related table I could not. See example:
table Categories and Table Subcategories
$categories = $this->repository->with(['subcategories'])->scopeQuery(function($query){
return $query->orderBy('cat_name','asc');
})->all();
but I have to bring also subcategories alphabetically, but I can not
Update validation rules are only used for the given input, but not on the existing model data.
This means that you are required to post all data even though the data exists in the model.
Do you, @andersao, think it would make sense change this behaviour, so that the posted data replaces the current model's data (only the fields posted) and before updating it pulls all of the model's data and validates it against the update rules?
Hello,
So in you package, you fire some events when the model is updated, deleted or created.
Example :
A random model is updated. The repository fire an event : RepositoryEntityUpdated.
But what if the listener should have a different response depending on the action ?
Maybe, I want to send an email when the "status" attribute is modified and a different one, when the "type" attribute is modified.
Maybe, I want to fire an event depending on the route.
But the listener always catch the same event right ? (RepositoryEntityUpdated)
So is there any clean solution here ? Or do we have to fire our own events ?
Thanks,
provider
class BackendServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('CustomerRepositoryInterface', 'CustomerRepository');
}
}
Controller:
use CustomerRepositoryInterface;
class CustomerController extends BaseController {
protected $customer;
public function __construct(CustomerRepositoryInterface $customer)
{
$this->customer= $customer;
}
customer repository:
class CustomerRepository extends BaseRepository implements CustoemrRepositoryInterface
{
//... custom method here...
}
interface:
interface CustoemrRepositoryInterface extends RepoistoryInterface{
//... custom interface here...
}
is it better?
I see you have in your require-dev
double requirements for illuminate/http
. Also, any requirements in require
are also loaded in a dev environment, so requiring illuminate/support
in require-dev
is not necessary I believe.
Is it possible to make the validation optional? I'd like to use the default Laravel, but the prettus/laravel-validation
is pulled in anyway. Perhaps it could be optional, so people who want to use it will pull it in themselves? Or am I being a bit selfish with this and are many using this way of validating?
A last question, why are illuminate/http
, illuminate/pagination
and illuminate/database
only in the require-dev
?
How can I use for other parameter settings in RequestCriteria
database column = da3_cod
requestCriteria
my params
?cod=2030
It is possible to change the parameter settings.
l5-repository compatible with Laravel 4.2?
When you have to pass a Model to an Event you use ->skipPresenter()
, this causes a problem when you need to Present the data afterwards because you have to get the information from the database again to get it in the correct format.
Would it be possible to Transform the Model without making a query again?
Hello,
Maybe it is a good idea to add the after / before hooks for delete / create / update ?
Something like :
public function update(array $attributes, $id)
{
$this->applyScope();
if ( !is_null($this->validator) ) {
$this->validator->with($attributes)
->setId($id)
->passesOrFail( ValidatorInterface::RULE_UPDATE );
}
$_skipPresenter = $this->skipPresenter;
$this->skipPresenter(true);
$model = $this->model->findOrFail($id);
// before
$methodBefore = 'beforeUpdate';
if (method_exists($this, $methodBefore)) {
$result = call_user_func_array([$this, $methodBefore], [$model, $attributes]);
if ($result === false) return $result;
}
$model->fill($attributes);
$model->save();
// after
$methodAfter = 'afterUpdate';
if (method_exists($this, $methodAfter)) {
$result = call_user_func_array([$this, $methodAfter], [$model, $attributes]);
if ($result === false) return $result;
}
$this->skipPresenter($_skipPresenter);
$this->resetModel();
event(new RepositoryEntityUpdated($this, $model));
return $this->parserResult($model);
}
Then implement the required method in the repository :
class UserRepository extends BaseRepository
{
/**
* Specify Model class name
* @return string
*/
function model()
{
return \TrackerApi\Models\User::class;
}
public function beforeUpdate($model, $attributes)
{
// Before
}
public function afterUpdate($model, $attributes)
{
// after
}
}
What do you think ?
Hello, i've discovered a minor bug when I've been trying something like this:
$repository->findWhere([ ['key', '=', 'value'] ]);
but it doesn't work because of this (line 299 in BaseRepository.php)
if( is_array($value) )
{
list($field, $condition, $value) = $value;
$this->model = $this->model->where($field,$condition,$value);
}
In fact, in the given example, $field contains 'v', $condition contains 'a' and $value contains 'value'
i'm not sure if this is a PHP bug or not, we can see that PHP starts by assigning the last variable, so $value = $value[2] = 'value', then it uses the new $value so we get $condition = $value[1] = 'a' and $field = $value[0] = 'v'
it can be easily fixed just by using new variables like this
if( is_array($value) )
{
list($field, $condition, $val) = $value;
$this->model = $this->model->where($field,$condition,$val);
}
Cheers 😄
Hi,
Right now there doesn't seem to be a way to get paginated results either after a query (ex. $repo->find(1)->paginate()
) or via Fractal.
If this is actually possible, can you provide a little more documentation on how to do it? Otherwise, this would be a great feature to add.
This package has no tests at all. I think that it is crucial for a good package to have extensive tests.
I also think it would be beneficial to add them as soon as possible. There will be more features in this package soon and more features will only mean more tests to write.
Either I don't see the method, or the type hinting for the return is incorrect, how would one do the equivalent of whereX(1)->where('y','>',150)
or something similar?
What would be the best way to test repositories (with L5.1)?
Do you have tests for this package, I do see a phpunit configuration file.
How can I achieve this?
I need to add where clausules depending Input vars. Something like:
$users = $this->repository->where(function($query){
if (Input::get('email') != "")
$query->where('email', '=', Input::get('email'));
if (Input::get('type') != "")
$query->where('type', '=', Input::get('type'));
})->paginate($this->per_page);
I try with this but didn't work
$users = $this->repository->where(function($query){
if (Input::get('email') != "")
$query->where('email','<>', Input::get('email'));
if (Input::get('type') != "")
$query->where('type','<>', Input::get('type'));
return $query;
})->paginate($this->per_page);
Also with this, and didn't work
$users = $this->repository->findWhere([['id', '>', 1]])->paginate($this->per_page);
Any help, please?
I really enjoy using this package in my Lumen app.
I also really like the concept of criterias and how you can push them when you need them.
But I'm not sure to go around using them when it comes to relations.
I have a few tables, let's say articles and images.
All articles and images are tagged. So I also have a tags table, article_tag and image_tag table.
My current way to get articles tagged X is to use define a method on the Tag class:
public function latest_news()
{
return $this->belongsToMany('App\Article')
->select([
'articles.id',
'articles.start_date',
'articles.article_type',
'articles.header',
'articles.manchet',
'articles.image_id'
])
->where('articles.status', 'published')
->where('articles.article_type', 'news')
->where('lang_id', config('lang_id'))
->orderBy('start_date', 'desc')
->limit(3);
}
And then do this:
$tags->with(['latest_news'])->find(1234)
What do you recommend, @andersao ?
class BookTransformer extends TransformerAbstract
{
protected $defaultIncludes = [
'author'
];
...
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
How to response the error from ValidatorException when using restful API? Now I only got HTTP/1.0 500 Internal Server Error and show
When using multiple fields in repository's $fieldSearchable, softDelete-conditions do not work.
The cause is fairly obvious. Example of somewhat broken scenario:
$fieldSearchable = ['name', 'email'];
SELECT id, name, email FROM registered_users WHERE deleted_at IS NULL AND name LIKE '%.com%' OR email LIKE '%.com%';
If the keyword hits any field after the first OR in the query, deleted_at IS NULL -condition (which is always the first condition in any Eloquent-based query) doesn't really do anything.
Hello!
How are you using this method?
Why are you calling the Eloquent::first()? https://github.com/andersao/l5-repository/blob/master/src/Prettus/Repository/Eloquent/Repository.php#L125
$departments = $this->department->findByField('country_id',$country)->get() this line runs twice:
select * from `departments` where `departments`.`deleted_at` is null and `country_id` = ? limit 1 | 2
select * from `departments` where `departments`.`deleted_at` is null | undefined"
Hi,
I'm using doc example https://github.com/andersao/l5-repository#enabling-validator-in-your-repository but not happening.
Laravel version: 5.1.13
L5-repository: 2.1.4
Laravel-validation: 1.1.4
<?php
namespace SON\Validators;
use \Prettus\Validator\LaravelValidator;
class ChapterValidator extends LaravelValidator
{
protected $rules = [
'name' => 'min:3',
'order' => 'required',
];
}
<?php
//...
class ChapterRepositoryEloquent extends BaseRepository implements ChapterRepository
{
//..
public function validator()
{
return "SON\\Validators\\ChapterValidator";
}
}
<?php
//...
public function store($courseId)
{
//...
return redirect()->route('admin.courses.chapters.index', ['courseId' => $courseId]);
}
Thank's!
Hello, how should i manage to do something like this:
$category->find(1)->categoryLocalization()
->save(new CategoryLocalization( [
'name' => 'My Category in english',
'lang'=>'en'
] ));
According to the documentation findWhereIn
and findWhereNotIn
should be defined in the RepositoryInterface
. But they are not.
This seems to be a simple mistake and I'll add a PR to fix it.
Hi,
Can you add a new tag for the lattest commits?
The find method using findOrFail is what i need!
Thanks
I'm currently working on a fairly large project that I would like to put this as an underlying library. I have approximately 26 models. In theory, I don't see anything wrong with getting every model extracted to the repository and able to do validation. Granted, I probably don't need to add validation for States
/Countries
as I dont see any new ones being created in the near future. However, I'm not sure this is a small or simple task no matter what way you look at it. Thoughts as to the best way to go about doing that?
edit: even just a way to combine the repository and validation into a common file would (in theory) reduce the work by 50%.
Hello,
The RequestCriteria allow to search via query parameters.
What about search multiple values for the same field ?
api/role?search=FIELD:VALUE1;VALUE2 => return only VALUE2
api/role?search=FIELD:VALUE1;FIELD:VALUE2 => return only VALUE2
api/role?search=FIELD:VALUE1;VALUE2&searchFields=FIELD:IN => return nothing
api/role?search=FIELD:VALUE1;FIELD:VALUE2&searchFields=FIELD:IN => return nothing
Thanks
Hello,
It seems that the criteria are not working with the cache.
E.g :
// cache the records for the all() method
$records = $modelRepository->all();
// Could be withTrashed or whereHas or custom where criteria
$modelRepository->pushCriteria(\Namespace\CustomCriteria($params));
// Ignore the criteria and fetch the records from the cache
$records = $modelRepository->all();
We could use the getByCriteria() method but we can't use this method with multiple criteria.
Thanks
I am using a forked version of this repository to implement multitenant. This means my models have use TenantScopedModelTrait
on them. In this trait there is the following eloquent override.
/**
* Override the default findOrFail method so that we can rethrow a more useful exception.
* Otherwise it can be very confusing why queries don't work because of tenant scoping issues.
*
* @param $id
* @param array $columns
*
* @throws TenantModelNotFoundException
*/
public static function findOrFail($id, $columns = ['*'])
{
try {
return parent::query()->findOrFail($id, $columns);
} catch (ModelNotFoundException $e) {
throw with(new TenantModelNotFoundException())->setModel(get_called_class());
}
}
The BaseRepository of course does not have this method, so running the code below results in the two different errors represented
$id = 2500 //An order ID that does not exist in my current `orders` table
// Exception message = "No query results for model [Hello\Orders\Order] when scoped by tenant."
$TenantModelNotFoundException = Order::findOrFail($id);
// Exception message = "Call to undefined method Hello\Orders\Repositories\OrderRepository::findOrFail()"
$FatalErrorException = $this->order->findOrFail($id);
I'm wondering what you believe the best way for me to resolve this would be. Should I extend BaseRepository and add a findOrFail() method?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.