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

3

16

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.

Densmore answered 20/1, 2016 at 11:6 Comment(3)
Technically you don't have to use Eloquent as your ORM with Laravel it is just what is built in. If you don't like the way Eloquent generates objects then use an ORM that you do like or write your own code and then disable Eloquent.Yamada
Any suggestion for ORM? @YamadaDensmore
Not sure, you would have to look at all the different structures that get returned by different ORM's to find one that you like and feel like isn't wasting memory. The largest PHP based ORM that I know of is Doctrine.Yamada
S
5

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;
}
Shipowner answered 22/1, 2016 at 15:22 Comment(4)
I now understand why it is set that way, but is it possible to disable this feature? (i.e. just behaves like normal object, don't need to save previous value, only saves to database when using save() function)Densmore
According to the lib, there is no function to disable this. But for performance, you may limit the number of key necessary by getting only the needed key, like $user = User::where('name', 'John Doe')->get('email'); This will reduce the size of the array. Another option is not to use Eloquent as @Yamada refered.Shipowner
Any suggestion for ORM that do what I mentioned?Densmore
Never done this but try with the normal DB class and in the relations set the needed join queries. Something like this: pastebin.com/zCSBaysCShipowner
A
3

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...

Ara answered 25/1, 2016 at 5:13 Comment(0)
A
3

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

Antiserum answered 28/1, 2016 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.