I have three table "questionaires", "questions" and "PivotQuestionaires". "questionaires" and "questions" are connected via foreign key through "PivotQuestionaires".
One questionaire *hasMany questions and question *belongsTo certain questionaire.
For example, if i have certain record in "PivotQuestionaires" which states: "Questionaire with id of 2 has questions with ids of 1,2 and 3 and if i want to change that to have only one specific question with for example with id 14. How would i do it?
Or if did it in different direction, if pivot has questionaire with only one question, but i want to update it with several?
What i'm trying to ask, questionaire should have ability to have variable number of questions.
How to achieve such update? Is there a "Laravel" of doing this or should i devise specific logic for it?
Here is screenshot of schema to better visualize
Kudos in advance.
Its a many to many relation between questionnaire and question like
class Questionnaire extends Model
{
public function questions()
{
return $this->belongsToMany(Question::class, 'pivot_questionnaire', 'questionnaire _id', 'question_id');
}
}
class Question extends Model
{
public function questionnaires()
{
return $this->belongsToMany(Questionnaire::class, 'pivot_questionnaire', 'question_id','questionnaire_id');
}
}
So if you want to assign multiple questions to questionnaire you can use attach & sync
$questionIds = [4,5];
$questionnaire->questions()->attach($questionIds);
this will add new associations [4,5] to existing ones like if previously if questionnaire has [1,2,3] then after attach() questionnaire will have [1,2,3,4,5]
$questionnaire->questions()->sync($questionIds);
If you use sync() it will removed all previous associations and add these new associations
Same goes for questions
$questionnaireIds = [4,5];
$question->questionnaires()->attach($questionnaireIds);
// OR
$question->questionnaires()->sync($questionnaireIds);
I have two models customer and orders. They are already fecthed separately
$customers = customer::all();
$orders = orders::all();
customerID=1 has orderID : 1, 2,4 customerID=2 has orderID : 3,5,9
They are related (hasMany, belongsTo) but the problem is inside my for a certain reason they are separated but I want to send them as response in API using toJson or ToArray as one data having the orders nested to their correct customers.
How can I achieve that linking to have at the end one variable $customersWithOrders that should be transformed to JSON ?
I am using laravel 5.5
I don't know what the context is. Defining relationships as other answers mentioned is a good solution.
In addition, I recently read a pretty good article about this specific scenario.
So you can also do something like this, if you have already retrieved customers and orders:
$customers = Customer::all();
$orders = Order::all();
return $customers->each(function ($customers) use ($orders) {
$customer->setRelation('orders', $orders->where('customer_id', $customer->id));
});
If you already have a relation you just use it. For example, in model Customer.php:
public function orders()
{
return $this->hasMany(Order::class);
}
Then you'd get customer orders by calling $customer->orders
If you already have defined relations, you can simply fetch data with eager loading
// in customer model
public function orders()
{
return $this->hasMany(orders::class, 'orderID');
}
// in controller
$customersWithOrders = customer::with('orders')->get();
return response()->json(['customersWithOrders' => $customersWithOrders]);
// in js
for (let customer in response.customersWithOrders){
let orders = customer.orders
}
Hello guys I am using Laravel 5.6 I have three tables in the database posts,comments and replies and three models Post , Comment , Reply
the relations are as follow one post has many comments and one comment has many replies. I created a route that when hit will return some data ;however, I want this data to be in a specific way read this example:
Lets say I have 6 posts in my database and each post has 6 comments also each comment has 6 replies I want to return only the first 3 posts along with the first 3 comments for each post also the first 3 replies for each comment
//this is a function inside a controller
//and for sure I have make sure to make use of the models namespaces
public function test(){
$posts = Post::with(['comments' => function($data){
return $data->take(3);
},
'comments.replies' => function($data){
return $data->take(3);
}])->paginate(3);
//returning the posts
return $posts
}
This way is working it returns the first 3 post and it returns the first 3 comments and first 3 replies only for the first post but for other posts I only get an empty key of comments so there is no replies as a result
hope you get my question please help
sorry for big question
Thanks in advance.
There is no native support for this in Laravel.
I created a package for it: https://github.com/staudenmeir/eloquent-eager-limit
Use the HasEagerLimit trait in both the parent and the related model.
class Post extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
class Comment extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
Then you can apply ->take(3) to your relationship.
The same goes for the replies.
This question already has answers here:
Laravel eloquent counting a relation
(3 answers)
Closed 5 years ago.
I am trying to make a query where I get the number of comments on a News in a row in laravel.
News Table
News_Comments Table
Display count of comments in 'Kommentare'
This is the query in mysql.
SELECT news.Id,news.title,news.body,news.created_at, COUNT(news_comments.id) AS Kommentare
FROM news
LEFT JOIN news_comments ON news_comments.fk_news_id = news.id
GROUP BY news.id
Result of this query
How would I write that in laravel 5.5?
Thank you very much!
You have to set the relationship between News and NewsComments.
In News Model, supposing that news_comments is in NewsComment Model, in App folder. Set the relationship:
public function newsComments()
{
return $this->hasMany('App\NewsComment', 'fk_news_id');
}
And then, to get the count, you can use count() method:
$count = $news->newsComments()->count();
Or, directly using blade:
{{ $news->newsComments()->count() }}
Make a model for news table name News and another for comments table name Comment. And then in your News model, define a hasMany() relationship method like -
public function comments(){
return $this->hasMany(Comment::class);
}
And then in your controller, retrieve like this-
$newses = News::with('comments')->get();
This will load all news with corresponding all comments. If you want to load single news with all comments then -
$news = News::with('comments')->where('id',$news_id)->first();
To get the comment count, you can count() on relationship like -
$count = News::find($news_id)->comments()->count();
I want to implement page view counter in my app. What I've done so far is using this method :
public function showpost($titleslug) {
$post = Post::where('titleslug','=',$titleslug)->firstOrFail();
$viewed = Session::get('viewed_post', []);
if (!in_array($post->id, $viewed)) {
$post->increment('views');
Session::push('viewed_post', $post->id);
}
return view('posts/show', compact('post', $post));
}
I retrieve the popular posts list like this :
$popular_posts = Post::orderBy('views', 'desc')->take(10)->get();
However, I'd like to know if there are any better ways to do this ? And with my current method, can I get a list of most viewed posts in the past 24 hours ? That's all and thanks!
As quoted in # milo526's comment, you can record all hits to your pages in a unique way instead of an increment. With this you have many possibilities to search for access information, including the listing of the posts sorted by most viewed.
Create a table to save your view records:
Schema::create("posts_views", function(Blueprint $table)
{
$table->engine = "InnoDB";
$table->increments("id");
$table->increments("id_post");
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->string("user_id");
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then, create the corresponding model:
<?php namespace App\Models;
class PostsViews extends \Eloquent {
protected $table = 'posts_views';
public static function createViewLog($post) {
$postsViews= new PostsViews();
$postsViews->id_post = $post->id;
$postsViews->titleslug = $post->titleslug;
$postsViews->url = \Request::url();
$postsViews->session_id = \Request::getSession()->getId();
$postsViews->user_id = \Auth::user()->id;
$postsViews->ip = \Request::getClientIp();
$postsViews->agent = \Request::header('User-Agent');
$postsViews->save();
}
}
Finally, your method:
public function showpost($titleslug)
{
$post = PostsViews::where('titleslug', '=' ,$titleslug)->firstOrFail();
PostsViews::createViewLog($post);
//Rest of method...
}
To search the most viewed posts in the last 24 hours:
$posts = Posts::join("posts_views", "posts_views.id_post", "=", "posts.id")
->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
->groupBy("posts.id")
->orderBy(DB::raw('COUNT(posts.id)', 'desc'))
->get(array(DB::raw('COUNT(posts.id) as total_views'), 'posts.*'));
Note that in PostsViews, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.
You may need to adapt some aspects of this solution to your final code.
2020 Update (2)/ With Eloquent Relationships for Laravel 6
If you don't want to add a package to your application. I have developed the following solution based on "Jean Marcos" and "Learner" contribution to the question and my own research.
All credit goes to "Jean Marcos" and "Learner", I felt like I should do the same as Learner and update the code in a way the would be beneficial to others.
First of all, make sure you have a sessions table in the database. Otherwise, follow the steps in Laravel documentations to do so: HTTP Session
Make sure that the sessions are stored in the table. If not, make sure to change the SESSION_DRIVER variable at the .env set to 'database' not 'file' and do composer dump-autoload afterwards.
After that, you are all set to go. You can start by running the following console command:
php artisan make:model PostView -m
This will generate both the model and migration files.
Inside of the migration file put the following Schema. Be cautious with the columns names. For example, my posts table have the "slug" column title name instead of the "titleslug" which was mentioned in the question.
Schema::create('post_views', function (Blueprint $table) {
$table->increments("id");
$table->unsignedInteger("post_id");
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->unsignedInteger('user_id')->nullable();
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then put the following code inside the PostView model file.
<?php
namespace App;
use App\Post;
use Illuminate\Database\Eloquent\Model;
class PostView extends Model
{
public function postView()
{
return $this->belongsTo(Post::class);
}
public static function createViewLog($post) {
$postViews= new PostView();
$postViews->post_id = $post->id;
$postViews->slug = $post->slug;
$postViews->url = request()->url();
$postViews->session_id = request()->getSession()->getId();
$postViews->user_id = (auth()->check())?auth()->id():null;
$postViews->ip = request()->ip();
$postViews->agent = request()->header('User-Agent');
$postViews->save();
}
}
Now inside the Post model write the following code. This to create the relation between the posts table and the post_views table.
use App\PostView;
public function postView()
{
return $this->hasMany(PostView::class);
}
In the same Post model you should put the following code. If the user is not logged in the the code will test the IP match. Otherwise, it will test both the session ID and the user ID as each user might have multiple sessions.
public function showPost()
{
if(auth()->id()==null){
return $this->postView()
->where('ip', '=', request()->ip())->exists();
}
return $this->postView()
->where(function($postViewsQuery) { $postViewsQuery
->where('session_id', '=', request()->getSession()->getId())
->orWhere('user_id', '=', (auth()->check()));})->exists();
}
You are ready now to run the migration.
php artisan migrate
When the user ask for the post. The following function should be targeted inside the PostController file:
use App\PostView;
public function show(Post $post)
{
//Some bits from the following query ("category", "user") are made for my own application, but I felt like leaving it for inspiration.
$post = Post::with('category', 'user')->withCount('favorites')->find($post->id);
if($post->showPost()){// this will test if the user viwed the post or not
return $post;
}
$post->increment('views');//I have a separate column for views in the post table. This will increment the views column in the posts table.
PostView::createViewLog($post);
return $post;
}
As I have a separate column for views in the post table. To search the most viewed posts in the last 24 hours you write this code in the controller. Remove paginate if you don't have pagination:
public function mostViwedPosts()
{
return Posts::with('user')->where('created_at','>=', now()->subdays(1))->orderBy('views', 'desc')->latest()->paginate(5);
}
I hope this would help/save someones time.
2020 Update
First of all, thanks a lot "Jean Marcos" for his awesome answer. All credit goes to him, I am just pasting a slightly modified answer combining my knowledge of Laravel as well.
Create a table to save your view records and name it with snake_case plural: post_views
Schema::create("post_views", function(Blueprint $table)
{
$table->engine = "InnoDB";//this is basically optional as you are not using foreign key relationship so you could go with MyISAM as well
$table->increments("id");
//please note to use integer NOT increments as "Jean Marcos' answer" because it will throw error "Incorrect table definition; there can be only one auto column and it must be defined as a key" when running migration.
$table->unsignedInteger("post_id");//note that the Laravel way of defining foreign keys is "table-singular-name_id", so it's preferable to use that
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->unsignedInteger('user_id')->nullable();//here note to make it nullable if your page is accessible publically as well not only by logged in users. Also its more appropriate to have "unsignedInteger" type instead of "string" type as mentioned in Jean Marcos' answer because user_id will save same data as id field of users table which in most cases will be an auto incremented id.
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then, create the corresponding model. Please note to create "PascalCase" model name and singular form of the table so it should be like: PostView
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PostView extends Model
{
public static function createViewLog($post) {
$postViews= new PostView();
$postViews->listing_id = $post->id;
$postViews->url = \Request::url();
$postViews->session_id = \Request::getSession()->getId();
$postViews->user_id = (\Auth::check())?\Auth::id():null; //this check will either put the user id or null, no need to use \Auth()->user()->id as we have an inbuild function to get auth id
$postViews->ip = \Request::getClientIp();
$postViews->agent = \Request::header('User-Agent');
$postViews->save();//please note to save it at lease, very important
}
}
Then run the migration to generate this table
php artisan migrate
Finally, your method:
public function showpost($titleslug)
{
$post = PostView::where('titleslug', '=' ,$titleslug)->firstOrFail();
\App\PostView::createViewLog($post);//or add `use App\PostView;` in beginning of the file in order to use only `PostView` here
//Rest of method...
}
To search the most viewed posts in the last 24 hours:
$posts = Posts::join("post_views", "post_views.id_post", "=", "posts.id")
->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
->groupBy("posts.id")
->orderBy(DB::raw('COUNT(posts.id)'), 'desc')//here its very minute mistake of a paranthesis in Jean Marcos' answer, which results ASC ordering instead of DESC so be careful with this line
->get([DB::raw('COUNT(posts.id) as total_views'), 'posts.*']);
Note that in PostView, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.
You may need to adapt some aspects of this solution to your final code.
So those were few modifications I wanted to point out, also you might want to put an additional column client_internet_ip in which you can store \Request::ip() which can be used as a filter as well if required.
I hope it helps
Eloquent Viewable package can be used for this purpose. It provides more flexible ways to do stuff like this(counting page views).
Note:The Eloquent Viewable package requires PHP 7+ and Laravel 5.5+.
Make Model viewable:
Just add the Viewable trait to the model definition like:
use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\Viewable;
class Post extends Model
{
use Viewable;
// ...
}
Then in the controller:
public function show(Post $post)
{
$post->addView();
return view('blog.post', compact('post'));
}
After that you can do stuff like this:(see package installation guide for more details)
// Get the total number of views
$post->getViews();
// Get the total number of views since the given date
$post->getViews(Period::since(Carbon::parse('2014-02-23 00:00:00')));
// Get the total number of views between the given date range
$post->getViews(Period::create(Carbon::parse('2014-00-00 00:00:00'), Carbon::parse('2016-00-00 00:00:00')));
// Get the total number of views in the past 6 weeks (from today)
$post->getViews(Period::pastWeeks(6));
// Get the total number of views in the past 2 hours (from now)
$post->getViews(Period::subHours(2));
// Store a new view in the database
$post->addView();
Implements same kind of idea as in the accepted answer, but provides more features and flexibilities.
First of all thanks to user33192 for sharing the eloquent viewable. Just want to make it clearer for others after looking at the docs. Look at the docs to install the package.
Do this in your Post Model:
use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\InteractsWithViews;
use CyrildeWit\EloquentViewable\Viewable;
class Post extends Model implements Viewable
{
use InteractsWithViews;
// ...
}
In your posts controller, use the record method to save a view;
public function show($slug)
{
$post = Post::where('slug',$slug)->first();
views($post)->record();
return view('posts.show',compact('post'));
}
In your views you can return the views (mine is posts.show) as you want. Check the document for more. I will just the total views of a post.
<button class="btn btn-primary">
{{ views($post)->count() }} <i class="fa fa-eye"></i>
</button>