Skip to content

Instantly share code, notes, and snippets.

@Artistan
Created November 5, 2018 16:21
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Artistan/50cc06379fc2ab829ad00313fd0a8b61 to your computer and use it in GitHub Desktop.
Save Artistan/50cc06379fc2ab829ad00313fd0a8b61 to your computer and use it in GitHub Desktop.
Make a model dynamically appendable.

Inspired by https://stackoverflow.com/questions/47222168/setappends-on-relation-which-is-being-loaded-by-with-in-laravel/53158181#53158181

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

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.

<?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();
}
}
<?php
namespace App\Database;
abstract class Company
{
use Appendable;
}
<?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);
@thearyanahmed
Copy link

This was pretty handy. Nice one.

@yadavsaurabh7
Copy link

Hello this is not working for me Am I doing something wrong I am using datatables and followed the same steps that were mentioned above
here is the code snippet from my controller

$replaceCurrentAppendsArray = true;
// this will remove the original appends by replacing with empty array
Product::setStaticAppends([],$replaceCurrentAppendsArray);
Brand::setStaticAppends([],$replaceCurrentAppendsArray);
ProductCategory::setStaticAppends([],$replaceCurrentAppendsArray);
$query = Product::with(['categories', 'tags', 'brand', 'created_by'])
->select(
'products.*'
);

@matmarlow
Copy link

This is perfect! Big thanks for writing this. Here's how I used it:

I need to get competitor details linked to a User, but there are loads of stats details that are pulled using custom attributes that I don't normally want in the appends array. They're only useful on the profile page because they will slow down other queries where I just want the basic competitor info.

So, when I eager load the competitor relationship on the user profile request, I needed to specify that I want the stats too... bingo!

UserController.php

class UserController extends Controller
{
    public function profile()
    {
        // specify the extra custom attributes we need for the profile
        Competitor::setStaticAppends(Competitor::$statisticsAppends,false);
        
        // load the user and eager load the competitor relation
        return User::find(Auth::user()->id)->with([
            'competitor'
        ])->first();
    }
}

Competitor.php


class Competitor extends BaseEloquentModel{
    ...

    static $statisticsAppends = [
        'first_competition',
        'comps_attended',
        'num_comps_attended',
        'series_attended',
        'num_series_attended',
        'highest_position_ever',
    ];

    ...
}

@joy2fun
Copy link

joy2fun commented May 4, 2023

Works like a charm!
I think an ! mark should be added to line 37.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment