`setAppends([])` on relation which is being loaded by `with` in Laravel

Viewed 6081

So for my project model setAppends([]) works as below:

Project::find($projectId)->setAppends([])

but what if I want to set appends to empty array for a relation which I'm eager loading with with, like below:

$project = Project::with('pages')->find($projectId);

->setAppends([]) not working in above code, as it will set it to empty array for Project not for Page.

Can anyone guide how to achieve that ?

Update:

page.php Model has appends and hidden like this:

class Page extends Model {
    // I don't want to load this (`appends`) attributes when I call Project::find($projectId)
    protected $appends = ['thumbnail_url', 'total_annotations', 'total_tasks', 'total_done_tasks', 'image_url', 'edited_data_items_count'];
    protected $hidden = ['tasksCount', 'doneTasksCount', 'annotationsCount', 'xsl', 'xml', 'dataxml_version', 'sort_order', 'editedDataItemsCount', 'deletedDataItemsCount'];
}

Project.php model looks like this:

class Project extends Model {

    use SoftDeletes;

    protected $appends = ['total_tasks', 'total_done_tasks', 'total_pages', 'total_annotations', 'edited_dataitems_total_count'];
    protected $hidden = ['tasksCount', 'doneTasksCount', 'pagesCount', 'annotationsCount', 'folder_path', 'attachment_url', 'pages'];
}
2 Answers

On Project you may provide a static method, which allows you to iterate over the eagerly loaded pages and adjust their append-array.

    class Project
    {
        ... 

        public static function eagerFindWithoutAppends($projectId) 
        {
            $model = self::with('pages')->find($projectId);
            $model->setAppends([]);

            foreach ($model->pages as $page) {
                $page->setAppends([]);
            }

            return $model;
        }

        ...        
    }

But if I understand correctly, the dynamic data in your Pages class does more than just providing convenient shortcuts based on the regularly loaded data (such as something like getFullName which would combine first_name and last_name).

What do your appends do?

I don't want to load this (appends) attributes

Another possible solution I could think of is to inherit NoneAppendPages from Pages and override $append and all the related get... methods.

Then in Project declare another relationship to NoneAppendPages next to Pages. You then eager load Project::::with('none_append_pages')->find($projectId);

class NoneAppendPages extends Pages
{

    protected $appends = [];

    getYourDynamicAttributeMethodName() { return null; } // for all your appends

}

class Project
{
    public function pages()
    {
       // I don't know what relationship you declared / assuming on to many
        return $this->hasMany('App\Page');
    }

    public function noneAppendPages()
    {
       // declare the same way you did with pages
        return $this->hasMany('App\NoneAppendPage');
    }

}

The given solution does not work when using a package that does a lot of the work after you define the with() relations like datatables

here is a solution that works for any model.

<?php

namespace App\Database;

trait Appendable {

    static protected $static_appends = [];
    static protected $static_replace_appends = null;

    /**
     * set a static appends array to add to or replace the existing appends array..
     * replace => totally replaces the existing models appends array at time of calling getArrayableAppends
     * add => merges and then makes unique. when getArrayableAppends is called. also merges with the existing static_appends array
     *
     * @param $appendsArray
     * @param bool $replaceExisting
     */
    public static function setStaticAppends($appendsArray, $replaceExisting = true)
    {
        if($replaceExisting) {
            static::$static_replace_appends = true;
            static::$static_appends = array_unique($appendsArray);
        } else {
            static::$static_replace_appends = false;
            static::$static_appends = array_unique(array_merge(static::$static_appends,$appendsArray));
        }
    }

    /**
     * Get all of the appendable values that are arrayable.
     *
     * @return array
     */
    protected function getArrayableAppends()
    {
        if(!is_null(static::$static_replace_appends)) {
            if(static::$static_replace_appends) {
                $this->appends = array_unique(array_merge(static::$static_appends,$this->appends??[]));
            } else {
                $this->appends = static::$static_appends;
            }
        }
        return parent::getArrayableAppends();
    }

}

then you can just apply the trait to any model

<?php

namespace App\Database;

abstract class Company
{
    use Appendable;
}

then call the static method BEFORE you use the relationship

<?php

$replaceCurrentAppendsArray = true;
// this will remove the original appends by replacing with empty array
\App\Database\Company::setStaticAppends([],$replaceCurrentAppendsArray);

$replaceCurrentAppendsArray = true;
// this will remove the original appends by replacing with smaller array
\App\Database\Company::setStaticAppends(['thumbnail_url'],$replaceCurrentAppendsArray);

$replaceCurrentAppendsArray = FALSE;
// this will add to the original appends by providing an additional array element
\App\Database\Company::setStaticAppends(['my_other_attribute'],$replaceCurrentAppendsArray);

this will allow you to override the appends array provided on the model even if another package is going to be loading the model. Like yajra/laravel-datatable where my issue was and brought me to this page which inspired a more dynamic solution.

This is similar to Stefan's second approach, but this is more dynamic so you do not have to create additional model extensions to accomplish the overrides.

You could take a similar approach to override the HidesAttribute trait as well.

Related