Laravel scope using calculated value - php

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.

Related

how to get specific date result in laravel?

example i am passing this date in $date = 2020-12-28 15:15:53
and
in db approve_date = 2020-12-28 15:15:00
i am trying to get all the record of date only like this 2020-12-28
so i tried
public function getdatedInvoice($date)
{
$invoices = Invoice::where('user_id' , Auth::id())->where('is_approved' , 1)->whereDate('approve_date' , $date)->get();
dd($invoices);
return view('approved_invoices', compact('invoices'));
}
but when i try to use whereDate it gives me nothing how i can get that data according to date?
First of all, by default, Laravel will only process the approve_date column from your database as a string, even if you set it as a date_time column.
To make Laravel process it as a real date instead, you need to add this to the top of your Invoice model:
class Invoice extends Model {
protected $dates = [
'approve_date'
];
}
Now you will be able to make date comparisons without getting weird errors.
To make your date formatted the way you want, you can go about it in 2 ways.
You can either set a default date formats on every date column in your model by adding this also to the model:
protected $dateFormat = 'Y-m-d';
https://laravel.com/docs/5.2/eloquent-mutators#date-mutators
You can also do this at runtime in your view: {{ \Carbon\Carbon::parse($invoice->approve_date)->format('Y-m-d') }}
'approve_date' is not a variable... you are missing the $ sign. It should be something like this:
$invoices = Invoice::where('user_id' ,
Auth::id())->where('is_approved' , 1)->whereDate('$approve_date' ,
$date)->get();
that variable is not being declared in the function;
After all that, you have your date like date/time and you should convert the format using (for example) Carbon https://carbon.nesbot.com/docs/

appending a column on db result in Laravel

I am returning some results in my laravel app where I want to add a friendly time column to the returned results rather than showing the timestamp.
What I wanting to do is if the timestamp is today to show the time it was saved i.e. 09:45, if the created yesterday based on todays date, then I want to show "Yesterday" and if it is old than yesterday then I want to show a date "12/6/2020". I just cannot work out the logic using Carbon, here is my logic so far,
protected $appends = ['friendly_date'];
public function getFriendlyDateAttribute()
{
if($this->created_at < Carbon::now()->subDays("1") {
return "Yesterday";
}
}
Carbon offers a utility function to do this:
protected $appends = ['friendly_date'];
public function getFriendlyDateAttribute()
{
if ($this->created_at->isToday()) {
return $this->created_at->format('H:i');
}
if($this->created_at->isYesterday()) {
return "Yesterday";
}
return $this->created_at->format('d/m/y'); // Assuming this is what 12/6/2020 means
}

Laravel where with Carbon addMinutes not working

I have a table representing events, each of which has a notice period, e.g. you can't book the event if it's currently less than 24 hours before the event.
I'm trying to create a 'bookable' scope for this, but am failing. Specifically, in the below, 'time' represents the time of the event (timestamp), and 'notice' the notice period, in minutes (integer), both of which are columns in the Events model. What I've found is that Laravel is not reading the 'notice' variable, i.e. treating it as 0. Any guidance would be appreciated, thanks.
public function scopeBookable($q) {
$q->where('time','>',Carbon::now()->addMinutes('notice'))->orderBy('time','ASC')->get();
}
The addMinutes() method expects an integer not a string.
Scope Option
You can pass the notice time through to the scope.
// Controller
$notice = 60;
Events::bookable($notice);
// Model
public function scopeBookable($q, $notice=0) {
$q->where('time','>',Carbon::now()->addMinutes($notice))->orderBy('time','ASC')-get();
}
Collection Option
You can always execute a self-join in SQL and check the value of notice in a subquery. Another option is to return a filtered eloquent collection.
public function scopeBookable() {
return Events::all()->filter(function($event) {
return $event->time > Carbon::now()->addMinutes($event->notice)
});
}

How to get the correct total number of orders in Magento

I've noticed that there are several ways to get the total number of orders in Magento, using getSize(), for loop, or count(), however, they provide different results within the same time range.
I'd like to know what is the best way to get the correct total number of orders in Magento, and what is the difference between getSize(), for loop and count() when calculating the number of orders. Below is the code I used for testing between getSize() and for loop.
//get order total amount
$OrdersAmount_completed = Mage::getModel('sales/order')->getCollection()
->addAttributeToFilter('status',array('eq' => Mage_Sales_Model_Order::STATE_COMPLETE))
->addAttributeToFilter('created_at', array(
'from' => $timefrom,
'to' => $timeto,))
// ->getSize() //this generates a difference result with $num_completed
;
foreach ($OrdersAmount_completed as $order_completed)
{
$num_completed++;
}
Ideally get Size will always give you proper count. If you get into details, any getCollection() we refer to collection.php inside model/mysql folder.
Inside magento, collection.php extends Mage_Core_Model_Mysql4_Collection_Abstract which extends Mage_Core_Model_Resource_Db_Collection_Abstract which extends Varien_Data_Collection_Db
In Varien_Data_Collection_Db, we have getSize() function which is :
<?php
public function getSize()
{
if (is_null($this->_totalRecords)) {
$sql = $this->getSelectCountSql();
$this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams);
}
return intval($this->_totalRecords);
}
public function getSelectCountSql()
{
$this->_renderFilters();
$countSelect = clone $this->getSelect();
$countSelect->reset(Zend_Db_Select::ORDER);
$countSelect->reset(Zend_Db_Select::LIMIT_COUNT);
$countSelect->reset(Zend_Db_Select::LIMIT_OFFSET);
$countSelect->reset(Zend_Db_Select::COLUMNS);
$countSelect->columns('COUNT(*)');
return $countSelect;
}
?>
We can see, it is getting all the count from database depending upon the query.
getSize() will only fetch the count from database. If we get all collection and execute foreach, it is going to fetch all order objects which can be a huge query and also load on the database bandwidth. It is hence, not advisable to do so. :)
I've figured this one out. It's caused by time zone difference:
//get Magento time zone
echo "Magento System time:".date("m/d/Y h:i:s a", Mage::getModel('core/date')->timestamp(time()));
//get server time zone
$timezone = date_default_timezone_get();
echo "The current server timezone is: " . $timezone;
I've found the server time is using UTC (Coordinated Universal Time), and for Magento it's using something different.

Implementing date filter on laravel queries

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();
}

Categories