Eloquent next record - php

I am trying to get the last 3 blog post from the database into single variables (for templates). I saw a good implementation at some other thred and it works fine for the next record but on the third query returns with NULL. What is your opinion about this problem?
BlogController.php:
public function getIndex($l = 'hu')
{
$post_last = Post::orderBy('created_at', 'desc')->first();
$post_2 = $post_last->next($post_last->created_at);
$post_3 = $post_2->next($post_2->created_at);
var_dump($post_3);
}
Post.php:(Model)
<?php
namespace Civitas;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Physical table name
*/
protected $table = 'posts';
/**
* Get next result in record list
*
* #param $created_at
* #return mixed
*/
public function next($c) {
return Post::where('created_at', '<', $c)->get()->first();
}
}

I can't tell why your function doesn't work, but I suggest you try this approach:
$posts = Post::orderBy('created_at', 'desc')->take(3)->get();
$post1 = $posts->shift();
$post2 = $posts->shift();
$post3 = $posts->shift();
This will only run one query instead of three. Calling shift() on the collection will then return the first item and remove it so the second post will be "first" the next time you call it.

In your next function, the result will give the earliest post, not the next one. Therefore, the third call will return null because there is no post after the earliest one. Adding orderBy for created_at field, it will work as expected.
public function next(){
return static::where('created_at', '<' , $this->created_at)
->orderBy('created_at','desc')
->first();
}
Then in your controller, you can call like this:
$post_last = Post::orderBy('created_at', 'desc')->first();
$post2 = $post_last->next();
$post3 = post_last->next()->next();

Related

Doctrine fetch associated array for multiple objects

I've got two classes, Post and Comment, as components I'm using to build a news feed. Post has a one-to-many association with Comment.
When building my news feed, I query the posts table and typically return a chunk of 20 results. With these 20 results, I'd also like to load the comments associated with it. Logic dictates that I can simply use fetch="EAGER" on the #OneToMany annotation, but in this use case it doesn't scale well as searching through a comments table with 1,000's of entries repetitively for each post is not performant.
Ideally, I'd like to separately preload them in a single query - which is what I've done.
class Post {
// ...
/**
* #var Comment[]
*
* #ORM\OneToMany(targetEntity="App\Entity\PostComment", mappedBy="post", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $comments;
public function getComments() {
return $this->comments;
}
public function setComments(array $comments) {
$this->comments = $comments;
}
}
class NewsFeedService {
private function preloadComments(array $posts) {
$postIds = array_column($posts, 'id'); // Get the post ID's
$comments = $this->manager->getRepository(Comment::class)->findCommentByPostIds($postIds);
// Group the comments by post
$groupedComments = [];
foreach($comments as $comment) {
$groupedComments[$comment->getPost()->getId()][] = $comment;
}
// Populate each $post object with it's comments
foreach($groupedComments as $postId => $postComments) {
$post = $posts[$postId];
$post->setComments($postComments);
}
}
}
class CommentRepository extends EntityRepository {
public function findCommentsByPostIds(array $postIds) {
return $this->createQueryBuilder('c')
->where('c.post IN(:ids)')
->setParameter('ids', $postIds)
->getQuery()
->getResult();
}
}
Essentially, when calling preloadComments() I run a single query on the DB and then force the results into each Post. Compared to EAGER fetching this saves me on average 35-40% with my current dataset (60,000 comments). I mean it works, but..
My question is if there is a better, perhaps even doctrine native way of doing this. There are also some things that I haven't tested such as if calling $post->setComments() and adding externally fetched data will cause issues if I just so happen to update the Post object and flush the changes. I feel like going about the way I'm doing isn't optimal and may cause some small headaches over the performance I'm saving.

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

Laravel - Disable Updated At when updating

Get a problem with update using query builder on laravel 5. I've tried to disabled the updated_at but keep failing.
Here is my code:
$query = StockLog::where('stock_id', $id)->whereBetween('created_at', $from, $to])->update(['batch_id' => $max + 1]);
I've tried 2 ways:
first one at my model i set:
public function setUpdatedAtAttribute($value)
{
/*do nothing*/
}
Second one:
$stocklog = new StockLog;
$stocklog->timestamps = false;
$query = $stocklog::where('stock_id', $id)->whereBetween('created_at', [$from, $to])->update([
'batch_id' => $max + 1]);
both of them are failed. is there anyway to disabled the updated_at?
Thanks in advance
By default, Eloquent will maintain the created_at and updated_at columns on your database table automatically. Simply add these timestamp columns to your table and Eloquent will take care of the rest.
I don't really suggest removing them. But if you want use the following way.
add the following to your model:
public $timestamps = false;
This will disable the timestamps.
EDIT: it looks like you want to keep the created_at field, you can override the getUpdatedAtColumn in your model.
Use the following code:
public function getUpdatedAtColumn() {
return null;
}
In your model, add this method:
/**
* #param mixed $value
* #return $this
*/
public function setUpdatedAt($value)
{
return $this;
}
UPDATE: In Laravel 5.5:
Just try to use this in your model:
const CREATED_AT = null;
const UPDATED_AT = null;
The accepted answer didn't work for me, but led me in the right direction to this solution that did:
class Whatever extends Model {
//...
const UPDATED_AT=NULL;
//...
Laravel 5.3
In this case it's better to use Query Builder instead of Eloquent because Query Builder doesn't implicitely edits timestamps fields. The use of Query Builder will have the advantage of targetting only the concerned update operation without alterate all your model.
In one line you could do:
$query = \DB::table('stocklogs')->where('stock_id', $id)->whereBetween('created_at', [$from, $to])->update(['batch_id' => $max + 1]);
You can use following if you want make it off permanently.
Add following to your model...
public $timestamps = false;
And if you want to keep using created_at, then add following.
static::creating( function ($model) {
$model->setCreatedAt($model->freshTimestamp());
});
OR use following way...
/**
* Set the value of the "updated at" attribute.
*
* #param mixed $value
* #return void
*/
public function setUpdatedAt($value)
{
$this->{static::UPDATED_AT} = $value;
}
Before updating you need to add ->toBase()
For example
Model::query()->where([...])->toBase()->update([...]);
in your case it will be
StockLog::where('stock_id', $id)->whereBetween('created_at', $from, $to])->toBase()->update(['batch_id' => $max + 1]);

Laravel-eloquent: Call to undefined method Illuminate\Database\Eloquent\Collection::where()

I have two models in many-to-one relationship:
class Meal extends \Eloquent {
/**
* public Integer $id; - primary key
* public String $name;
*/
protected $fillable = array('id','name');
public function mealProperties()
{
return $this->hasMany('MealProperty');
}
}
class MealProperty extends \Eloquent {
/**
* public Integer $id; - primary key
* public Integer $meal_id;
*/
protected $fillable = array('id','meal_id');
public function meal()
{
return $this->belongsTo('Meal', 'meal_id');
}
}
if I ask for first meal first mealProperty everything go fine:
$mealProp = Meal::first()->mealProperties->first();
but if I ask for mealProperty with specific id of first meal this way:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
I get this error:
Call to undefined method Illuminate\Database\Eloquent\Collection::where()
I google what I'm doing wrong two hours, but still nothing.
If I can't use where method, what is possible way to get specific mealProperty?
Thank you for help!
UPDATE for Laravel 5:
Since v5 release there is a method where on the Support\Collection object, so this question/answer becomes irrelevant. The method works exactly like filter, ie. returns filtered collection straight away:
$mealProp = Meal::first()->mealProperties->where('id','=','1'); // filtered collection
// that said, this piece of code is perfectly valid in L5:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
You must distinguish Laravel behaviour:
(dynamic property) Eloquent Collection or Model
$meal->mealProperties
Relation Object
$meal->mealProperties()
Now:
// mealProperties is Eloquent Collection and you call first on the Collection here
// so basically it does not affect db query
$mealProp = Meal::first()->mealProperties->first();
// here you try to add WHERE clause while the db query is already called
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
// So this is what you want to do:
$mealProp = Meal::first()->mealProperties()->where('id','=','1')->first();
You may try this:
$mealProop1 = Meal::first()->mealProperties->find(1); // id = 1
Or something like this:
$mealProops = Meal::first()->mealProperties;
$mealProop5 = $mealProops->find(5); // id = 5
$mealProop7 = $mealProops->find(7); // id = 7
Instead of this:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
Also, following should work:
$mealProp = Meal::first()->mealProperties->first();

Working with a MY_Model

I’m using jamie Rumbelow’s MY model as a way to better deal with my application.
https://github.com/jamierumbelow/codeigniter-base-model
The MY_model is the same except I have an added in variable for defining whether or not an item in the db is marked as being soft deleted or not.
protected $soft_delete_value = 3;
I only have that variable defined and have not altered his code yet to account for this value.
I have two things I want to do with this titles model that I need help understanding.
Titles Table - title_id, title_name, title_status_id
Title_Statuses_Table - title_status_id, title_status_name
What I want it to do is retrieve all of the rows that have a title_status_id of 1 and 2 and 3 because the soft delete value is different than the default set in the MY Model. What I would also like to have is instead of it returning the integer have it return the name of the status.
Expected results:
An array of objects that contain a title_id, title_name, title_status_name for which the titles have a status id of 1,2, or 3.
Testing
$titles = $this->titles_model->get_all();
echo "<pre>";
print_r($titles);
echo "</pre>";
Actual results:
SELECT *
FROM (`titles`)
WHERE `title_status_id` = 0
<pre>Array
(
)
My Code
class Titles_model extends MY_Model
{
/* --------------------------------------------------------------
* VARIABLES
* ------------------------------------------------------------ */
/**
* This model's default database table.
*/
public $_table = 'titles';
public $primary_key = 'title_id';
/**
* Support for soft deletes and this model's 'deleted' key
*/
public $soft_delete = TRUE;
public $soft_delete_key = 'title_status_id';
public $soft_delete_value = 4;
public $_temporary_with_deleted = FALSE;
public function __construct()
{
parent::__construct();
}
}
Anybody else have any additional ideas/suggestions?
EDIT:
I've been tryign to figure this out all day and have hit a dead end.
well here would be the function that you would need to get your expected result
$this->db->select('
titles.*,
status.*,
')
->join('status s', 'titles.title_status_id = s.title_status_id', 'LEFT')
->where('titles.title_status_id', 1)
->or_where('titles.title_status_id', 2)
->or_where('titles.title_status_id', 3)
->from('titles')
->get()
->result_object();

Categories