Which is faster: save() or update() Laravel method? - laravel

I want to increment the calls / views for a blog. In the controller I have the following two lines:
$post->views = $post->views + 1;
$post->save();
I wonder if the update method might be faster?
Another question about incrementing. Is there a Laravel function that can be used to increment?

In my opinion, both writing methods should be equally fast. Here is your second question. You can use increment()function.
// 1.
Post::where('id', $id)->increment('views');
// 2.
Post::find($id)->increment('views');
// 3.
$post->views++;
$post->save();

Both methods will be broadly the same, update() also calls save().
From the Eloquent\Model API:
public function update(array $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
}
return $this->fill($attributes)->save($options);
}

Related

Laravel Test Case Error - Call to a member function where() on null

I have defined relationship in the ModelClass
public function allocations(): MorphMany
{
return $this->morphMany(
Allocation::class,
'allocatable',
'ref_class',
'ref_id'
)->where('is_active', 1); // this line is creating issue
}
Test Case
public function testAllocations(): void
{
// Arrange
$morphMany = $this->partialMock(MorphMany::class);
$model = $this->createPartialMock(
ModelClass::class,
['morphMany']
);
// Expectations
$model->expects($this->once())
->method('morphMany')
->with(
Allocation::class,
'allocatable',
'ref_class',
'ref_id'
)
->willReturn($morphMany);
// Action
$result = $model->allocations();
// Assert
$this->assertInstanceOf(MorphMany::class, $result);
}
Error: Call to a member function where() on null
If I remove where condition in the relationship, the test is passing correctly.
I need that where condition in the relationship. How can I pass where condition in TestCase?
Any suggestion will be great. Thanks :)
Have you tried something like this?
$data = $this->morphMany(
Allocation::class,
'allocatable',
'ref_class',
'ref_id'
);
return $data->where('is_active', 1);

Which design pattern helps me in this case using - Laravel

so I have a controller which returns a view with a DB Collection which I have a repository,
now, in case I have a request I want to return an array - json and not a view
any ideas if this is the correct approach?
public function index(
Request $request,
CampaignPerformanceRepository $campaignPerformanceRepository
) {
$data = $campaignPerformanceRepository->getDataByPeriod($request);
if ($request->all()) {
return $campaignPerformanceRepository->getDataByPeriod($request);
}
return view('reports.campaign-performances', compact('data'));
}
I do something similar in cases where I may want to return a view or just json based on the request. There's an easy helper function request()->wantsJson() which you can use.
$data = $campaignPerformanceRepository->getDataByPeriod($request);
if ($request->wantsJson()) {
return $campaignPerformanceRepository->getDataByPeriod($request);
}
return view('reports.campaign-performances', compact('data'));

Laravel dynamic scope only works first time

I'm missing something with how the global scopes work in Laravel 5.5.
In my controller, index , I am passing filters into a getter:
public function index(SaleFilters $filters)
{
return new SaleCollection($this->getSales($filters));
}
getSales:
protected function getSales(SaleFilters $filters)
{
$sales = Sale::with('office')->filter($filters);
return $sales->paginate(50);
}
protected function range($range)
{
$dates = explode(" ", $range);
if (count($dates) == 2) {
$this->builder = Sale::with(['office', 'staff'])
->where('sale_date', '>=', $dates[0])
->where('sale_date', '<', $dates[1])
->orderBy('sale_date', 'desc');
return $this->builder;
}
return false;
}
I have a scope setup in the sale model as such, which I would have thought would apply to the above filter automatically ? If not, do I have to reapply the same scope, duplicating the scope code in the filter ?
protected static function boot()
{
parent::boot();
$user = Auth::user();
if (($user) && ($user['office_id'])) {
return Sale::ofOffice($user['office_id'])->get();
}
}
public function scopeOfOffice($query, $office)
{
return $query->where('office_id', $office);
}
So basically, IF the user has an office_id applied, it should apply the ofOffice scope, therefore it should only ever return the sales that apply to that office_id.
Basically it works on page load via axios GET request
Route::get('/sales', 'SalesController#index')->middleware('auth:api');
axios
.get('api/sales/?range=" + this.rangeFilter)
rangeFilter is basically a start and end date passed into the above filter query.
Can anyone shed some light on how the scopes really work or if anything is obvious as to why its not always working? As I said, it works on page load where I provide default values for the rangeFilter, however when I change those days and it refetches via the same axios call, it seems to not be applying the scope, and I get ALL results instead of where office_id = 'x'
As far as i'm concerned, the range filter above would be executing on the first page load as well, so not sure why it would apply there, and not afterwards.
You should not mix the use of dynamic scope with global one. Also, static boot function does not expect a return. In order to use dynamic scope, you need to call it every time you need it. Hence, the name is dynamic. Query applied is not always executed by default. There so,
protected function getSales(SaleFilters $filters)
{
$sales = Sale::ofOffice($anyOfficeHere)->with('office')->filter($filters);
return $sales->paginate(50);
}
To suit your existing code, you may want to add an if statement in your model. Then call the scope function without argument.
public function scopeOfOffice($q)
{
if (($user = \Auth::user()) && ($office = $user->office_id)) {
$q->where('office_id', $office);
}
}
// Your controller
protected function getSales(SaleFilters $filters)
{
$sales = Sale::ofOffice()->with('office')->filter($filters);
return $sales->paginate(50);
}
If you feel so much cumbersome to type ofOffice repeatedly. A global scope is the way to go. Within your model static boot function, you can also apply anonymous function if you feel creating a separated class kinda bloat your apps.
protected static function boot()
{
parent::boot();
static::addGlobalScope('officeOrWhatNot', function ($q) {
if (($user = \Auth::user()) && ($office = $user->office_id)) {
$q->where('office_id', $office);
}
});
}
// Your controller. No more `ofOffice`, it's automatically applied.
protected function getSales(SaleFilters $filters)
{
$sales = Sale::with('office')->filter($filters);
return $sales->paginate(50);
}

How to achieve this on laravel 5 eloquent

How can i achieve something like this?
public function getInformation($model) {
$result = $model::with(['province', 'city']);
if($model == 'App\Models\Business') {
$result->with(['businessProvince', 'businessCity']);
}
$result->get();
}
// call the function
$information->getInformation(\App\Models\Business::class);
i'm getting error
Object of class Illuminate\Database\Eloquent\Builder could not be
converted to string
on the sample code above. Any suggestion is really appreciated.
After taking a fourth look $model should be a string, and $result is an Eloquent Builder instance and never an instance of the model class (since a query was started when with was called).
So the $model == 'App\Models\Business' I would change to $model === \App\Models\Business::class but that should not change the outcome.
Are you sure this error comes from this part of the application? Which line specifically?
Original wrong answer.
You are trying to compare the model instance with a string (since $model::with() created a instance of the model class you passed in the $model argument).
You can use the instanceof keyword for comparing an instance with a class name (http://php.net/manual/en/language.operators.type.php).
if($model instanceof \App\Models\Business) {
$result->with(['businessProvince', 'businessCity']);
}
This solved my problem, thank you guys.
public function getInformation($model) {
$result = $model::with(['province', 'city']);
if($model == 'App\Models\Business') {
// my mistake
//$result->with(['businessProvince', 'businessCity']);
$result = $result->with(['businessProvince', 'businessCity']);
}
$result->get();
}

Adding methods to Eloquent Model in Laravel

I'm a bit confused how I am to add methods to Eloquent models. Here is the code in my controller:
public function show($id)
{
$limit = Input::get('limit', false);
try {
if ($this->isExpand('posts')) {
$user = User::with(['posts' => function($query) {
$query->active()->ordered();
}])->findByIdOrUsernameOrFail($id);
} else {
$user = User::findByIdOrUsernameOrFail($id);
}
$userTransformed = $this->userTransformer->transform($user);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound('User does not exist');
}
return $this->respond([
'item' => $userTransformed
]);
}
And the code in the User model:
public static function findByIdOrUsernameOrFail($id, $columns = array('*')) {
if (is_int($id)) return static::findOrFail($id, $columns);
if ( ! is_null($user = static::whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
So essentially I'm trying to allow the user to be retrieved by either user_id or username. I want to preserve the power of findOrFail() by creating my own method which checks the $id for an int or string.
When I am retrieving the User alone, it works with no problem. When I expand the posts then I get the error:
Call to undefined method
Illuminate\Database\Query\Builder::findByIdOrUsernameOrFail()
I'm not sure how I would go about approaching this problem.
You are trying to call your method in a static and a non-static context, which won't work. To accomplish what you want without duplicating code, you can make use of Query Scopes.
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
if ( ! is_null($user = $query->whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
You can use it exactly in the way you are trying to now.
Also, you can use firstOrFail:
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
return $query->whereUsername($id)->firstOrFail($columns);
}
Your method is fine, but you're trying to use it in two conflicting ways. The one that works as you intended is the one in the else clause, like you realised.
The reason the first mention doesn't work is because of two things:
You wrote the method as a static method, meaning that you don't call it on an instantiated object. In other words: User::someStaticMethod() works, but $user->someStaticMethod() doesn't.
The code User::with(...) returns an Eloquent query Builder object. This object can't call your static method.
Unfortunately, you'll either have to duplicate the functionality or circumvent it someway. Personally, I'd probably create a user repository with a non-static method to chain from. Another option is to create a static method on the User model that starts the chaining and calls the static method from there.
Edit: Lukas's suggestion of using a scope is of course by far the best option. I did not consider that it would work in this situation.

Resources