Laravel Eloquent: Append queries - php

Is it possible to "append" queries in Laravel?
For example like so in this example code:
function crunchNumbersForToday()
{
return $this->crunchSomeInformationInRange(Payments::where('created_at', Carbon::now()));
}
function crunchNumbersInRange($range)
{
$paymentsToCrunch = Payment::where('state', 'payed')->append($range)->get();
// Work with the payments
}
So in this case the where of the created_at field would be appended to the query where state equals payed. Which would make something like: where created_at = '2015-07-08 12:00' AND state = payed.

I think you could create a scope.
For your code that would be (in your Payment model):
function scopeToday($query)
{
return $query->where('created_at', Carbon::now());
}
function scopeNumbersInRange($query)
{
return $query->where('state', 'payed');
// Work with the payments
}
And then call it elsewhere in your code like:
Payment::numbersInRange()->today()->get();
Edit:
You can make them dynamic too:
function scopeDate($query, $date)
{
return $query->where('created_at', $date);
}
...
Payment::numersInRange()->date(Carbon::now())->get();
And so on. This way you can keep chaining scopes.

Related

Laravel detect if there is a new item in an array

I want to implement a system in my project that "alerts" users when there is a new comment on one of their posts.
I currently query all comments on the posts from the logged in user and put everything in an array and send it to my view.
Now my goal is to make an alert icon or something when there is a new item in this array. It doesn't have to be live with ajax just on page load is already good :)
So I've made a function in my UsersController where I get the comments here's my code
public function getProfileNotifications()
{
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
//comments
if (!empty($projects)) {
foreach ($projects as $project) {
$comments_collection[] = $project->comments;
}
}
if (!empty($comments_collection)) {
$comments = array_collapse($comments_collection);
foreach($comments as $com)
{
if ($com->from_user != Auth::user()->id) {
$ofdate = $com->created_at;
$commentdate = date("d M", strtotime($ofdate));
$comarr[] = array(
'date' => $ofdate,
$commentdate,User::find($com->from_user)->name,
User::find($com->from_user)->email,
Project::find($com->on_projects)->title,
$com->on_projects,
$com->body,
Project::find($com->on_projects)->file_name,
User::find($com->from_user)->file_name
);
}
}
} else {
$comarr = "";
}
}
Is there a way I can check on page load if there are new items in the array? Like keep a count and then do a new count and subtract the previous count from the new one?
Is this even a good way to apprach this?
Many thanks in advance! Any help is appreciated.
EDIT
so I added a field unread to my table and I try to count the number of unreads in my comments array like this:
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
//comments
if (!empty($projects)) {
foreach ($projects as $project) {
$comments_collection[] = $project->comments;
}
}
$unreads = $comments_collection->where('unread', 1);
dd($unreads->count());
But i get this error:
Call to a member function where() on array
Anyone any idea how I can fix this?
The "standard" way of doing this is to track whether the comment owner has "read" the comment. You can do that fairly easily by adding a "unread" (or something equivalent) flag.
When you build your models, you should define all their relationships so that stuff like this becomes relatively easy.
If you do not have relationships, you need to define something like the following:
In User
public function projects()
{
return $this->hasMany('App\Models\Project');
}
In Project
public function comments()
{
return $this->hasMany('App\Models\Comment');
}
Once you hav ethose relationshipt, you can do the following. Add filtering as you see fit.
$count = $user->projects()
->comments()
->where('unread', true)
->count();
This is then the number you display to the user. When they perform an action you think means they've acknowledged the comment, you dispatch an asynchronous request to mark the comment as read. A REST-ish way to do this might look something like the following:
Javascript, using JQuery:
jQuery.ajax( '/users/{userId}/projects/{projectId}/comments/{commentId}', {
method: 'patch'
dataType: 'json',
data: {
'unread': false
}
})
PHP, in patch method:
$comment = Comment::find($commentId);
$comment->update($patchData);
Keep in mind you can use Laravel's RESTful Resource Controllers to provide this behavior.
try this
$unreads = $project->comments()->where('unread', 1);
dd($unreads->count());
EDIT
My be Has Many Through relation will fit your needs
User.php
public function comments()
{
return $this->hasManyTrough('App\Project', 'App\Comment');
}
Project.php
public function comments()
{
return $this->hasMany('App\Comment');
}
then you can access comments from user directly
$user->comments()->where('unread', 1)->count();
or I recommend you define hasUnreadComments method in User
public function hasUnreadComments()
{
$return (bool) $this->comments()->where('unread', 1)->count();
}
P.S.
$uid = Auth::user()->id;
$projects = User::find($uid)->projects;
this code is horrible, this way much better
$projects = Auth::user()->projects;

Laravel ORM select query function load() on null

Here's my function to load submissions created by a user.
public function viewSubs()
{
$user = User::find(Input::get('id'));
$submissions = Submission::find($user)->sortByDesc('created_at');
$submissions->load('user')->load('votes')->load('suggestions.votes');
return view('submissions.index' , compact('submissions'));
}
This returns with an error
Call to a member function load() on null
when there are no records on the submission.
How to handle if there are no submission on the DB?
Just check if its null first using an if statement:
public function viewSubs()
{
$user = User::find(Input::get('id'));
if ($submissions = Submission::find($user)->sortByDesc('created_at')) {
$submissions->load('user')->load('votes')->load('suggestions.votes');
}
return view('submissions.index' , compact('submissions'));
}
Also, depending on your DB structure I'm pretty sure you can cut out a lot of the code by utilising your models' relationships by doing something like this:
$user = User::find(Input::get('id'))
->with(['submissions' => function($query) {
$query->orderBy('created_at', 'asc');
}, 'submissions.votes', 'submissions.suggestions.votes']);
Then pass the $user variable to the view, or:
$submissions = Submission::with('user', 'votes', 'suggestions.votes')
->where('user_id', Input::get('id'))
->sortByDesc('created_at')
->first();
Not entirely sure the code will work perfectly, but I'm sure you can tweak it. The point is your code can be a lot shorter and still/or more readable by using relationships you've already set up.

Laravel relationship count()

I want to get a total user transaction (specific user) with relationship.
I've done it but i'm curious is my way is good approach.
//User Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
//Merchant Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
public function countTransaction()
{
return $this->hasOne(Transaction::class)
->where('user_id', Request::get('user_id'))
->groupBy('merchant_id');
}
public function getCountTransactionAttribute()
{
if ($this->relationLoaded('countTransaction'))
$this->load('countTransaction');
$related = $this->getRelation('countTransaction');
return ($related) ? (int)$related->total_transaction : 0;
}
//controller
$merchant = Merchant::with('countTransaction')->get();
What make me curious is part inside countTransaction. I put where where('user_id', Request::get('user_id')) directly inside the model.
is it good approach or any other way to get specific way?
expected result:
"merchant:"{
"name": "example"
"username" : "example"
"transactions": {
"count_transactions: "4" //4 came from a specific user.
}
}
I need to get the merchant data with the transaction count for specific user. This query is based on logged in user. so when a user access merchant page, they can see their transaction count for that merchant.
Thanks.
You really want to keep request data outside of your models (instead opting to pass it in). I'm also a little confused about why you have both a 'hasOne' for transactions, and a 'hasMany' for transactions within the merchant model.
I would probably approach the problem more like the below (untested, but along these lines). Again I'm not fully sure I understand what you need, but along these lines
// Merchant Model
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function countTransactionsByUser($userId)
{
return $this
->transactions()
->where('user_id', $userId)
->get()
->pluck('total_transaction')
->sum();
}
// Controller
$userId = request()->get('user_id');
// ::all() or however you want to reduce
// down the Merchant collection
//
$merchants = Merchant::all()->map(function($item, $key) {
$_item = $item->getAttributes();
$_item['transactions'] = [
'count_transactions' => $item->countTransactionsByUser($userId);
];
return $_item;
});
// Single total
// Find merchant 2, and then get the total transactions
// for user 2
//
$singleTotal = Merchant::find(2)
->countTransactionsByUser($userId);

Protect routes dynamically, based on id (laravel, pivot table)

This topic has been discussed a lot here, but I don't get it.
I would like to protect my routes with pivot tables (user_customer_relation, user_object_relation (...)) but I don't understand, how to apply the filter correctly.
Route::get('customer/{id}', 'CustomerController#getCustomer')->before('customer')
now I can add some values to the before filter
->before('customer:2')
How can I do this dynamically?
In the filter, I can do something like:
if(!User::hasAccessToCustomer($id)) {
App::abort(403);
}
In the hasAccessToCustomer function:
public function hasCustomer($id) {
if(in_array($id, $this->customers->lists('id'))) {
return true;
}
return false;
}
How do I pass the customer id to the filter correctly?
You can't pass a route parameter to a filter. However you can access route parameters from pretty much everywhere in the app using Route::input():
$id = Route::input('id');
Optimizations
public function hasCustomer($id) {
if($this->customers()->find($id)){
return true;
}
return false;
}
Or actually even
public function hasCustomer($id) {
return !! $this->customers()->find($id)
}
(The double !! will cast the null / Customer result as a boolean)
Generic approach
Here's a possible, more generic approach to the problem: (It's not tested though)
Route::filter('id_in_related', function($route, $request, $relationName){
$user = Auth::user();
if(!$user->{$relationName}()->find($route->parameter('id')){
App::abort(403);
}
});
And here's how you would use it:
->before('id_in_related:customers')
->before('id_in_related:objects')
// and so on

Form Validation w/ sql + codeigniter

I'm working on creating a callback function in codeigniter to see if a certain record exists in the database, and if it does it'd like it to return a failure.
In the controller the relevent code is:
function firstname_check($str)
{
if($this->home_model->find_username($str)) return false;
true;
}
Then in the model I check the database using the find_username() function.
function find_username($str)
{
if($this->db->get_where('MasterDB', array('firstname' => $str)))
{
return TRUE;
}
return FALSE;
}
I've used the firstname_check function in testing and it works. I did something like
function firstname_check($str)
{
if($str == 'test') return false;
true;
}
And in that case it worked. Not really sure why my model function isn't doing what it should. And guidance would be appreciated.
if($this->home_model->find_username($str)) return false;
true;
Given that code snippet above, you are not returning it true. If that is your code and not a typo it should be:
if($this->home_model->find_username($str)) return false;
return true;
That should fix it, giving that you did not have a typo.
EDIT:
You could also just do this since the function returns true/false there is no need for the if statement:
function firstname_check($str)
{
return $this->home_model->find_username($str);
}
So the solution involved taking the query statement out of if statement, placing it into a var then counting the rows and if the rows was > 0, invalidate.
Although this is a more convoluted than I'd like.
I find your naming kind of confusing. Your model function is called 'find_username' but it searches for a first name. Your table name is called 'MasterDB'. This sounds more like a database name. Shouldn't it be called 'users' or something similar? I'd write it like this :
Model function :
function user_exists_with_firstname($firstname)
{
$sql = 'select count(*) as user_count
from users
where firstname=?';
$result = $this->db->query($sql, array($firstname))->result();
return ((int) $result->user_count) > 0;
}
Validation callback function :
function firstname_check($firstname)
{
return !$this->user_model->user_exists_with_firstname($firstname);
}

Categories