vincent15000's avatar

withSum doesn't take into account the accessor

Hello,

In the Transaction model, I have an accessor.

protected function amount(): Attribute
{
    return Attribute::make(
        get: fn (?int $value) => $value ? floatval($value) / 100 : null,
        set: fn (float $value) => intval($value * 100),
    );
}

And I'd like to retrieve the sum of the amounts with this code.

$accounts = Account::
    withSum('transactions', 'amount')
    ->orderBy('name')
    ->get();

The problem is that withSum seems to retrieve the amounts directly from the datase, so it doesn't retrieve the amounts divided by 100.

Is it possible to force the query to pass by the accessor ?

How is it possible to solve this problem ?

Thanks for your help.

V

0 likes
9 replies
Sinnbeck's avatar

It is making a subquery so no. It isnt possible. You would need to do the * 100 afterwards

1 like
vincent15000's avatar

@Sinnbeck Ok thank you.

When I do this.

$accounts = Account::
    with('transactions')
    ->orderBy('name')
    ->get();

dd($accounts[0]->transactions->sum('amount'));

I see that the accessor is taken into account.

So I have solved this problem with this code by handling the collection.

$accounts = Account::
    with('transactions')
    ->orderBy('name')
    ->get()
    ->each(function ($item) {
        $item->transactions_sum_amount = $item->transactions->sum('amount');
    });
Sinnbeck's avatar

@vincent15000 Yes when you do the calculation in php it is used. But personally I would just do that in the database. No need to get every single record just to calculate a simple sum :)

But couldnt just do the /100 afterwards ?

$accounts = Account::query() //add query here to have ot look a bit nicer
    ->withSum('transactions', 'amount')
    ->orderBy('name')
    ->get()
    ->map(function($account) {
        $account->transactions_sum_amount = $account->transactions_sum_amount / 100;
        return $account;
    });
1 like
vincent15000's avatar

@Sinnbeck Well ... it should be quicker ... but I need to differenciate the transactions income vs expense via a boolean field in the table.

->each(function ($item) {
    $item->transactions_income_sum_amount = $item->transactions->sum(function ($transaction) {
        return $transaction->income ? $transaction->amount : null;
    });
    $item->transactions_expense_sum_amount = $item->transactions->sum(function ($transaction) {
        return !$transaction->income ? $transaction->amount : null;
    });
});

And I think that I can't customize withSum with a callback to do that.

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@vincent15000 You could perhaps do two withSums then ?

$accounts = Account::query()
    ->withSum(['transactions as transactions_income_sum_amount' => function($query) [
          $query->where('income', true);
    }, 'transactions as transactions_expense_sum_amount' => function($query) [
          $query->where('income', false);
    }], 'amount')
    ->orderBy('name')
    ->get()
    ->map(function($account) {
        $account->transactions_income_sum_amount = $account->transactions_income_sum_amount / 100;
        $account->transactions_expense_sum_amount = $account->transactions_expense_sum_amount / 100;
        return $account;
    });
1 like
Sinnbeck's avatar

@vincent15000 Ok weird. I havent never seen that happen in any of my apps. I dont think it matters much. Personally I use map when I want to change a collection and each if I want to execute some action for each item in a collection :)

1 like

Please or to participate in this conversation.