Why laravel model duplicates set of data and how (if possible) to have only one set of data?

It is convenient that laravel model provides a method that it can return results from another associated table.

For example, I have a table called item and another table called feedback, where the feedback table stores feedback of an item in the item table. So, to get the all feedback of item with id 1, I will do:

Item::find(1)->feedback;

And the following this the printout of the object returned.

Illuminate\Database\Eloquent\Collection Object
(    [items:protected] => Array
       (
           [0] => Feedback Object
               (
                   [table:protected] => feedback
                   [connection:protected] => 
                   [primaryKey:protected] => id
                   [perPage:protected] => 15
                   [incrementing] => 1
                   [timestamps] => 1
                   [attributes:protected] => Array
                       (
                           [id] => 1
                           [rma_id] => 3
                           [item_id] => 8
                           [quo_id] => 0
                           [case_id] => i2eM20160120
                           [line_no] => 000001
                           [content] => test
                           [status] => sent
                           [read] => 0
                           [sender] => Tester
                           [created_at] => 2016-01-20 18:03:44
                           [updated_at] => 2016-01-20 18:03:44
                       )

                   [original:protected] => Array
                       (
                           [id] => 1
                           [rma_id] => 3
                           [item_id] => 8
                           [quo_id] => 0
                           [case_id] => i2eM20160120
                           [line_no] => 000001
                           [content] => test
                           [status] => sent
                           [read] => 0
                           [sender] => Tester
                           [created_at] => 2016-01-20 18:03:44
                           [updated_at] => 2016-01-20 18:03:44
                       )

                   [relations:protected] => Array
                       (
                       )

                   [hidden:protected] => Array
                       (
                       )

                   [visible:protected] => Array
                       (
                       )

                   [appends:protected] => Array
                       (
                       )

                   [fillable:protected] => Array
                       (
                       )

                   [guarded:protected] => Array
                       (
                           [0] => *
                       )

                   [dates:protected] => Array
                       (
                       )

                   [touches:protected] => Array
                       (
                       )

                   [observables:protected] => Array
                       (
                       )

                   [with:protected] => Array
                       (
                       )

                   [morphClass:protected] => 
                   [exists] => 1
               )

       )

)

It works fine, and it shows that there is only one feedback on item with id 1.

What concerns me is that the dataset is duplicated in [attributes:protected] and [original:protected]. This is just a testing case and the real case will consist of thousands of feedback and having a duplicated dataset is a huge waste of memory. The dataset is not duplicated if I am using the DB::table('table_name') approach, but that is much less convenient.

Why does laravel need to duplicate the data in model?

And is there a way to make it return only one set of data?

Currently I am using ->toArray() to trim down the unnecessary data right after the query, but the memory usage is still there because laravel is still creating that set of data.

Answers


While it's hard to get a good example, it allows you to set attributes before definitely saving them. Probably good if you go through many functions and finally check if everything has been set correctly for final save without the need to store everything in separate variables.

Very small example:

$user = User::find(1);
print_r($user);
$user->name = 'John Doe';
print_r($user);
$user->save();
print_r($user());

Returns something like:

First print:

[attributes:protected] => Array
(
   [id] => 1
   [name] => 'Jimmy Doe'
   ...
)
[original:protected] => Array
(
   [id] => 1
   [name] => 'Jimmy Doe'
   ...
)

Second print:

[attributes:protected] => Array
(
   [id] => 1
   [name] => 'John Doe'
   ...
)
[original:protected] => Array
(
   [id] => 1
   [name] => 'Jimmy Doe'
   ...
)

Thrid print:

[attributes:protected] => Array
(
   [id] => 1
   [name] => 'John Doe'
   ...
)
[original:protected] => Array
(
   [id] => 1
   [name] => 'John Doe'
   ...
)

Only after the save() the data is actually being saved into the DB.

The Eloquent's syncOriginal() is fired up when a model is save()'d:

/**
 * Sync the original attributes with the current.
 *
 * @return $this
 */
public function syncOriginal()
{
    $this->original = $this->attributes;

    return $this;
}

The original data is stored in order to allow the model to perform dirty checking. Dirty checking is used internally to handle database updates.

If a model is not dirty and you try to save it, no update will be performed. If a model is dirty and you try to save it, only those fields that are dirty will be updated.

If you really wanted to get rid of this, you could override the syncOriginal() and syncOriginalAttribute() methods on the model. If you did this, though, it would mean that the model will always be considered dirty. getDirty() will always return all the attributes, and isDirty() will always return true.

If you use timestamps, you will also need to override the updateTimestamps() method, otherwise your updated_at and created_at fields would never get set.

class Feedback extends Model
{
    // ...

    public function syncOriginal()
    {
        return $this;
    }

    public function syncOriginalAttribute($attribute)
    {
        return $this;
    }

    protected function updateTimestamps()
    {
        $time = $this->freshTimestamp();

        $this->setUpdatedAt($time);

        if (! $this->exists) {
            $this->setCreatedAt($time);
        }
    }

    // ...
}

There may be other repercussions that aren't immediately apparent while reviewing the code.

Having said all this, though, if you're having this concern about memory, you may need to have a second thought about your approach and what you're trying to do. Do you really need to load 1000's of feedback all at once? Is this an operation that can be chunked? Is this work that would be better served by individual jobs in a queue? etc...


This should not be an issue considering how PHP works internally. Unless the 'attributes' are not modified, 'attributes' is just a pointer to the 'original' (or the other way around), so having both arrays takes up nearly the same amount of memory as having just one of them. This is why memory usage does not change when you do toArray().

Please see this link for details: http://blog.ircmaxell.com/2014/12/what-about-garbage.html


Need Your Help

How to add a library for JWS app.?

java java-ee glassfish glassfish-3 java-web-start

I'm deploying a swing client that require some external library to run.

Windows x64 (Intrusive) Singly Linked List

windows linked-list reverse-engineering windbg intrusive-containers

I'm currently trying to wrap my head around some Windows lookaside lists, and I'm seeing some memory addresses that are confusing me.