6
votes

Previously ORM's i've used have mapped database columns directly to class properties which allowed you to specific property visibility, just as you normally would to restrict access to certain properties e.g. passwords.

With Eloquent i can't seem to replicate this because database columns are mapped to the internal attributes array which contain no visibility.

My desire is to restrict the scope of access to a user password to only the object i.e. private.

Setting a class property with visibility doesn't work because this property is then outside the scope of the Eloquent model attributes and thus the property is not mapped to the column.

Eloquent $hidden and $guarded properties do not work as these deal with mass output (toArray, toJSON) and mass assignment rather than direct assignment.

I have attempted to use the accessors/mutators (getters/setters) in order to achieve this with mixed results.

Specifying the visibility on the accessor doesn't work because the accessor method called (e.g. getPasswordAttribute) is called from Eloquent\Model->getAttribute method and as such public/protected will always work and private will always fail regardless of where the attribute it accessed from.

What does work however is to stop the Eloquent accessor returning the attribute altogether so any request to $user->password or $user->getAttribute ('password') fails, and then having a separate method with visibility defined in order to return the attribute directly from the Eloquent attributes array only in the scope allowed e.g.

/**
 * Return password string only for private scope
 * @return string
 */

private function getPassword ()
{
    return $this->attributes['password'];
}

/**
 * Don't return password with accessor
 * @param string $password Password
 * @return void
 * @throws Exception
 */

public function getPasswordAttribute ($password)
{
    throw new Exception ('Password access denied');
}

This same approach also works for mutators (setters) for anyone wanting setter method visibility.

Does this seem correct or is there a better "Laravel-Approved" way of dealing with this? :)

1
hidden works for outputting model as array/json. There's no 'laravel` way of handling it, and why do you want that?Jarek Tkaczyk
I'm pretty sure with hidden you can still do $user->password from your controller (I think) which is what he's trying to preventSabrina Leggett

1 Answers

3
votes

I'm not aware of an 'approved' way of doing this, as such, but you could always override Eloquent's __get() magic method to check for private fields?

The debug_backtrace() check is a bit hacky; I couldn't actually get this to work as expected without, as the getPassword() method (or basically any method in that class calling $this->password) was still using __get. This just checks that the class calling __get is the class itself, rather than another.

It shouldn't be too inefficient as the in_array check would fail for non-private properties before it does the backtrace anyway. There's probably a better way of doing it, though!

private $private = array(
    'password'
);

public function __get($key)
{
    // check that the class calling __get is this class, and the key isn't 'private'
    if (in_array($key, $this->private) && debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] != get_class()) {
        throw new \Exception('Private');
    }

    // anything else can return as normal
    return parent::__get($key);
}

public function getPassword()
{
    // calling this method elsewhere should work
    return $this->password;
}