43

How do dynamically hide certain columns when returning an Eloquent object as JSON? E.g. to hide the 'password' column:

$users = User::all();
return Response::json($users);

I'm aware I can set protected properties in the model ($hidden or $visible), but how do I set these dynamically? I might want to hide or show different columns in different contexts.

mtmacdonald
  • 14,216
  • 19
  • 63
  • 99

10 Answers10

44
$model->getHidden();
$model->setHidden(array $columns);

$model->setVisible(array $columns);
Jarek Tkaczyk
  • 78,987
  • 25
  • 159
  • 157
  • If I `setHidden('something')`. Does the attribute `something`is remove form future representation to JSON? Should I set back the original hidden array? – hugomosh May 13 '16 at 23:26
  • 4
    @hugomosh it works in the instance scope, so `something` will be hidden for the instance ot was called on, unless you set it again. But no other models will be affected. – Jarek Tkaczyk May 16 '16 at 01:56
27

From Lavarel 5.3 Documentation :

Temporarily Modifying Attribute Visibility

If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns the model instance for convenient method chaining:

return $user->makeVisible('attribute')->toArray();

Likewise, if you would like to make some typically visible attributes hidden on a given model instance, you may use the makeHidden method.

return $user->makeHidden('attribute')->toArray();
Community
  • 1
  • 1
Allan Stepps
  • 1,047
  • 1
  • 10
  • 23
19

I've found a complete solution around the problem with using $model->setHidden(array $columns);

Lets say, for example, that you would like to decide in the controller exactly which fields to return. Updating only the model's hidden forces you to go over each model before you return an array of models for example. The problem becomes even worse when those models have relationships that you would also like to change. You have to loop over each model, set the hidden attribute, and then for each also set the relationships hidden. What a mess.

My solution involves creating a static member for each model that when present, updates the visible/hidden attribute just before the call to "toArray":

<?php

trait DynamicHiddenVisible {

    public static $_hidden = null;
    public static $_visible = null;

    public static function setStaticHidden(array $value) {
        self::$_hidden = $value;
        return self::$_hidden;
    }

    public static function getStaticHidden() {
        return self::$_hidden;
    }

    public static function setStaticVisible(array $value) {
        self::$_visible = $value;
        return self::$_visible;
    }

    public static function getStaticVisible() {
        return self::$_visible;
    }

    public static function getDefaultHidden() {
        return with(new static)->getHidden();
    }

    public static function geDefaultVisible() {
        return with(new static)->getVisible();
    }

    public function toArray()    {
        if (self::getStaticVisible())
            $this->visible = self::getStaticVisible();
        else if (self::getStaticHidden())
            $this->hidden = self::getStaticHidden();
        return parent::toArray();
    }

}

As an added bonus, I expose a way to the model's default hidden/visible that you may have set in your model's class.

Don't to forget to add the trait

class Client extends Eloquent {
     use DynamicHiddenVisible;
}

Finally, in the controller, before returning your model, decide on visible/hidden attributes:

public function getIndex($clientId) {
    // in this specific call, I would like to hide the "special_type" field of my Client model
    $hiddenFields = Client::getDefaultHidden();
    array_push($hiddenFields, "special_type");
    Client::setStaticHidden($hiddenFields);

    return Client::find($clientId)->toJson();
}
NiRR
  • 4,782
  • 5
  • 32
  • 60
  • 1
    Thanks man! Awesome answer! If you change the if guards to use ```is_array``` as in ```if (is_array(self::getStaticHidden()))```, you will be able to pass an empty array to the setter methods and hide/show all fields at once. – Xavier Ojeda Aguilar Apr 27 '17 at 15:07
  • 1
    This awesome solution has inspired me to widen it a little, I made this [gist](https://gist.github.com/grey-dev-0/c8fc17778674ec4131dc7caa73254f30) where you can dynamically set more properties of a model not just "hidden" or "visible". Cheers. – Mohyaddin Alaoddin May 03 '18 at 11:05
11

In 5.4 you can hide and show attributes dinamically:

$model->makeVisible('attribute');

$model->makeHidden('attribute');

Laravel docs

Luca C.
  • 11,714
  • 1
  • 86
  • 77
10

I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.

You can do all of that with Fractal which I built for exactly this reason.

<?php namespace App\Transformer;

use Acme\Model\Book;
use League\Fractal\TransformerAbstract;

class BookTransformer extends TransformerAbstract
{
    /**
     * List of resources possible to include
     *
     * @var array
     */
    protected $availableIncludes = [
        'author'
    ];

    /**
     * Turn this item object into a generic array
     *
     * @return array
     */
    public function transform(Book $book)
    {
        return [
            'id'    => (int) $book->id,
            'title' => $book->title,
            'year'    => (int) $book->yr,
            'links'   => [
                [
                    'rel' => 'self',
                    'uri' => '/books/'.$book->id,
                ]
            ],
        ];
    }

    /**
     * Include Author
     *
     * @return League\Fractal\ItemResource
     */
    public function includeAuthor(Book $book)
    {
        $author = $book->author;

        return $this->item($author, new AuthorTransformer);
    }
}

Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.

Phil Sturgeon
  • 30,637
  • 12
  • 78
  • 117
  • 1
    How would you add / remove fields on a per request basis e.g. – AndrewMcLagan Jun 30 '16 at 12:07
  • `/book/22//chapters?fields[]=words&fields[]=color` – AndrewMcLagan Jun 30 '16 at 12:08
  • What is the advantage of using Fractal over manually looping and creating json structure, apart from cleaner looking code? – Fahmi Sep 12 '16 at 09:45
  • @Fahmi Hey there! I'm the original author of Fractal. At it's very basics, this is a structured, reusable array_map, done in a class you can tuck away. It gains far more power when you get into pagination/cursors and include/relationship logic. – Phil Sturgeon Sep 13 '16 at 14:59
  • @PhilSturgeon I've actually implemented it into my latest project and I seem to like it. Nice job :) – Fahmi Sep 13 '16 at 15:56
  • JSON returned is too complex, data is enveloped in "data" which is enveloped in "original" and there are several other unwanted properties like "headers" etc. The whole point is to strip array unwanted items not remove some and add some. – user3718908x100 Apr 13 '17 at 15:58
8

In addition to @deczo's answer - I feel the $hidden variable is not really designed to be used dynamically. It is more to protect specific data from ever been incorrectly displayed (such as 'password').

If you want specific columns - you should probably just be using a select statement and just get the specific columns you want.

Laurence
  • 58,936
  • 21
  • 171
  • 212
  • Why is that? `visible/hidden` fields are supposed to work exactly like this - to hide or not attributes that we don't want to include in `toArray/toJson` output. Not the only way of course. – Jarek Tkaczyk Jul 15 '14 at 13:34
  • 1
    Well - for the same reason that "select" exists - to get the columns you want to display/not display. I'm not saying you shouldnt do it - but I dont feel like it was the way it was designed. i.e. I think your answer is correct - this is just an alternative thought. – Laurence Jul 15 '14 at 13:38
  • Just asking about your opinion, no worries. I find it odd to use `select` when you work with ORM models, that's why. – Jarek Tkaczyk Jul 15 '14 at 13:46
  • @The Shift Exchange Which of the two techniques would you pick if we extended the problem to eager loading (dynamically hiding columns in a related model)? – mtmacdonald Jul 15 '14 at 13:49
3

For Laravel 5.3 or greater version,

If you want to make multiple attributes temporary hidden or visible using single statement, you may use model->makeVisible() and model->makeHidden() methods with passing array of attributes.

For example, to hide multiple attributes,

$user->makeHidden(["attribute1", "attribute2", "attribute3"]);

And to make visible multiple attributes,

$user->makeVisible(["otherAttribute1", "otherAttribute2", "otherAttribute3"]);
Dev
  • 6,570
  • 10
  • 66
  • 112
3

You can override the getHidden method in order to hide certain columns dynamically:

class FooModel extends Model
{
    public function getHidden()
    {
        // do here your validations and return
        // the columns names with the specific criteria
        // you need

        return ['columnName1', 'columnName2'];
    }
}
pableiros
  • 14,932
  • 12
  • 99
  • 105
2

In the Model:

protected $hidden = [
    'your_field_1',
    'your_field_2',
];
0

Made a package for this that uses Model Policies.

https://github.com/salomoni/authorized-attributes


Use the Salomoni\AuthorizedAttributes trait

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Salomoni\AuthorizedAttributes;

class Post extends Model
{
    use AuthorizedAttributes;

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = ['author_comments'];
}

Create and register a model policy. Add methods for the hidden attributes in camel-case prefixed with see.

namespace App\Policies;

use App\User;

class PostPolicy
{
    /**
     * Determine if a post author_comments-atrribute can be seen by the user.
     *
     * @param  \App\User  $user
     * @return bool
     */
    public function seeAuthorComments(User $user)
    {
        return $user->isAuthor();
    }
}
Jari Pekkala
  • 847
  • 7
  • 8