I'm trying to count unread messages in laravel app, however my "last_viewed" column contains "raw" time in seconds stored in "last_viewed (int) 11" and my "created_at" contains mysql timestamp so I can't compare them in mysql statement. Here is how I compare them, but it's kind of spaghetti
class Helper {
public static function count_unread_messages()
{
$result = Conversation::join('messages','messages.conversation_id','=','conversation.id')
->join('conversation_members','conversation_members.conversation_id','=','conversation.id')
->select('conversation_members.last_viewed as last_viewed',
'messages.created_at as created_at')
->where('conversation_members.user_id','=',Auth::user()->id)
->groupBy('messages.id')->get();
$i = 0;
foreach ($result as $key) {
if (date($key->last_viewed) < date(strtotime($key->created_at))) {
$i++;
}
}
return $i;
}
}
but I'd like to compare inside Mysql statement, like that
->where('last_viewed' ,'<', 'created_at')->count()
Is there a way to change format of a timestamp into seconds inside a mysql statement?
I think the best way to solve this is to persist time stamps as carbon instances.
You can do that by setting protected $dates = ['last_viewed', 'created_at']; in your Conversation model.
Now when the data get persisted, since last_viewed and created_at persists as carbon instances and mainly because eloquent support carbon, you can simply achieve
->where('last_viewed', '<', 'created_at')->count() functionality.
Hope this was helpful.
Related
I have the following problem in Laravel 5.4:
There´s a user table and a membership table, where a user can have many memberships, and a membership belongs to a user. The memberships are of annual duration, but a member will get an additional free day for each friend they recommend that registers on my site, thus the number of free days is constantly changing; this in turn changes the membership´s expiration date.
So the question is: How to scope the active memberships for a given user, if the expiration date is a variable?
I will need to do something like this:
First the expiration date in Membership.php:
This calculates the total days for each membership:
Note that the friendsDays are calculated per user in User.php
public function getTotalDaysAttribute() {
$days = $this->paidDays + $this->user->friendsDaysRemaining;
return $days;
}
This calculates the expiration date for each membership:
public function getExpirationDateAttribute() {
$date = $this->startDay->addDays($this->TotalDays);
return $date;
}
So far so good... Now, this is where I´m stuck (pseudo code):
public function scopeActive($query, $dateToCheck = Null) {
$query->where($dateToCheck >= $this->expirationDate);
}
How to code this properly to get:
dump($user->membership()->active()->get());
Thanks in advance.
You have two problems:
You are trying to use model values in your scope. Scopes happen before the model values have been set. This is because in order to get the values in the model to be set the query which fetches the data must first be executed.
Unfortunately due to your database design you won't be able to create a scope to get the answer you want. This is because you are using values on a different table to calculate your TotalDays value.
I suggest you change expirationDate value in the database and call it when a friend is invited.
Something like:
function addDaysToExpiration(User $user) {
$user->expirationDate = date('Y-m-d h:m:s', strtotime('2008-10-05' . '+1 day'));
$user->save();
}
You can pass variable to scope, so for example you can define scope like this:
public function scopeActive($query, \Carbon\Carbon $dateToCheck = Null)
{
$query->where('some_date_field', '>=' ($expirationDate ?? now())->toDateTimeString());
}
and then you can do:
$dateToCheck = now()->addDays(30);
dump($user->membership()->active($dateToCheck)->get());
You can also pass only number of days to scope instead of Carbon instance if it's more convienient to use in your case.
With the API you defined for yourself:
$user->membership()->active()->get();
Your method scopeActive won't be able to see related User and friendsDaysRemaining variable that you need for calculating the expiration date. You can try it for yourself:
public function scopeActive($query) {
var_dump($this->id); // null
var_dump($this->user); // null, this part will try to do the query: select * from users where id = null
}
In your position, I would probably go with a persisted expiration_date column on the memberships table and update it whenever needed. This would then allow you to do smth like:
public function scopeActive($query) {
return $query->where('expiration_date', '>', Carbon::now());
}
Thank you guys for your prompt answers. I figured it out using a different approach, based on your ideas. Since I cannot use calculated fields in the query, I went back to a field that do exists in the DB, this is the renewalDueDate, that´s a year from the payment date; both are known and fixed dates. Then, in the query I pass the $user and the $dateToCheck as parameters, substract the remaining friends days and compare to that value, like this:
public function scopeActive($query, $user, $dateToCheck = Null) {
// If no date is passed, use today()
$dateToCheck = is_null($dateToCheck) ? Carbon::today() : Carbon::parse($dateToCheck);
//Substract the friendsDaysRemaining from the dateToCheck
$AdjustedEndDate = $DateToCheck->copy()->subDays($user->friendsDaysRemaining);
//Build the query
$query ->where('paid', 1) //its been paid for
->where('startDay', '<=', $DateToCheck) //It has started
->where('renewalDueDate', '>=', $AdjustedEndDate); //It has not expired
return $query;
}
Although is cumbersome to have to pass the user to get the remaining friends days, this is now working fine:
$dateToCheck= '2018-09-01';
dump($user->membership()->active($user, $dateToCheck)->pluck('id'));
Result:
Collection {#299 ▼ #items: array:2 [▼
0 => 83
1 => 6 ] }
Of course you could also pass the $friendsDaysRemaining instead of the $user, but is also far from elegant.
Thanks again.
I am coding a feature in Laravel where a user can use a select box to choose a certain date. By standard, these dates are saved in y-m-d in my table.
I have succeeded in formatting the date to dd/mm/yyyy by using the Eloquent solution, adding to my Eloquent Model called Dataslot:
protected $dates = ['maand'];
Afterwards I could use the following to format the date.
$d = Dataslot::find(1);
$dformat = $d->maand->format('d-m-Y');
In order to pass the ID of the dataslot and the date to my select box, I have chosen to use the lists method.
$dataslots = Dataslot::lists("maand","id");
This returns me an array. I cannot use the format method on this array as I could on the object in the first example.
How can I have an array of formated dates, with the ID as a key, to send to my view?
Untested (I'm on mobile right now) but this should work;
Carbon::setToStringFormat('d-m-Y');
$dataslots = Dataslot::lists('maand', 'id');
You could change Carbon back after with:
Carbon::resetToStringFormat();
this did the trick:
$dataslots = Dataslot::all();
foreach ($dataslots as $o) {
$flatArray[$o->id] = $o->maand->format('d-m-Y');
}
Would this do it?
$dataslots = Dataslots::all()->map(function($dataslot) {
return [$dataslot->id => $dataslot->maand->format('d-m-Y')];
});
I'm storing time in a Mysql database, and by default it stores in the format 00:00:00
I'm using this to retrieve it:
public function events(){
return $this->hasMany('App\Event');
}
$events = $user->events()->orderBy('time')->get();
Now the problem is that I want to return it without the seconds, just in 00:00 format. Is there a laravel way to convert this easily without having to create a loop and convert each record manually?
i think u just need to define an accessor for this field
http://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
like this (in your user model)
public function getTimeAttribute($value)
{
$time = Carbon::createFromFormat('H:i:s', $value);
return $time->format('H:i');
}
In my Symfony/Doctrine application, I would like any timestamp fields in my database to retreived as PEAR Date objects instead of date strings. e.g. If my schema is
SomeEvent:
columns:
id:
type: integer
primary: true
start: timestamp
end: timestamp
I would like to be able to run a Doctrine query to retrieve SomeEvent objects and have $anEvent->getStart() be a PEAR Date object. Right now Doctrine gives me strings for all timestamp fields which is basically useless. (I'd also like saving of Dates to work correctly.)
What's the best way to accomplish this?
I researched using a Hydration listener but it looks like I'd have to register that per table and hardcode the column names that I want to be converted. Using a custom Hydrator didn't look much better since then I lose the ability to use any of the other core hydration methods without having my dates be strings again.
EDIT: It looks like Doctrine 2 has a feature that's exactly what I'm looking for: Custom Mapping Types. Unfortunately this site is being deployed to a host that doesn't support PHP 5.3+ so Doctrine 2 is out. :(
I figured out a hacky way to do it that I'm not super happy with, but it works. I created the following 2 classes:
class DateClassBehavior extends Doctrine_Template {
public function setTableDefinition() {
$listener = new DateClassListener();
$table = $this->getTable();
foreach ($table->getColumns() as $columnName => $columnDef) {
if ($columnDef['type'] == 'timestamp') {
$listener->dateColumns[] = $columnName;
}
}
$this->addListener($listener);
}
}
class DateClassListener extends Doctrine_Record_Listener {
public $dateColumns = array();
public function postHydrate(Doctrine_Event $event) {
$data = $event->data;
foreach ($this->dateColumns as $col) {
$date = $data->$col == null ? null : new Date($data->$col);
$data->$col = null;
$data->$col = $date;
}
$event->data = $data;
}
}
And added DateClassBehavior to the definition for each table in my model:
SomeEvent:
actAs: [DateClassBehavior]
columns:
id:
type: integer
primary: true
start: timestamp
end: timestamp
This takes care of creating the Date objects when things are loaded. I also modified the actual PEAR Date class (I know... bad) and added a __toString() method that returns $this->getDate(). PHP then automatically converts the dates to the correct string when doctrine saves them.
If anyone finds a better way to do this please post it.
I am implementing a date filter on my orders table e.g. return all orders that where 'created_at' today, last week, last month, on a specific date or between two dates.
I know that laravel has dedicated where methods such as "whereDay", "whereMonth" and others.
So far I have written a big switch statement for each condition like his:
switch($date_filter){
case 'today':
$orders = Order::whereDay('created_at','=',date('d')->get();
break;
// case for each date filter type
}
My question is whether there is generate the date filter part of the query separately, so I could do something like this:
$orders = Order::whereRaw($this->getDateFilterAsSql($date_filter)->get();
so that I would not have to repeat the entire query for each date_filter type?
You can create multiple local scopes and use them:
$this->order->$date_filter()->get();
An example of a local scope:
public function scopeToday($query)
{
return $query->whereDay('created_at', date('d'));
}
You also can use a model as a repository and do this:
$this->order->$date_filter();
An example of a method in Order model:
public function today()
{
return $this->whereDay('created_at', date('d'))->get();
}