JSON编码具有受保护属性的Eloquent属性

I have models with attributes which contains objects of classes with protected properties. I want to output the model in json including the protected properties.

I am working with these constraints:

  1. Said classes are from a third party library which I cannot edit, so I cannot add in a JsonSerializable interface.
  2. I am using the attributes as arguments for the library, so I don't want to extend them and use an accessor. If I were to extend it I would have to convert it back for every time I use it as an argument for the library. So I am trying to avoid that.

-

class MyModel extends Model {}
class Pen {
    protected $colour = 'red';
}
class Library {
    public static function pineapple(Pen $pen) {
    }
}

$myModel->pen = (new Pen);

// I need to use them for a third party library so I should not change the class
Library::pineapple($myModel->pen);


// In controller
return response()->json([
            'data' => $myModel
        ]);

I want the output to contain {pen: { colour: 'red' }}

Assuming there is some method on the Pen class to get the $colour attribute (e.g. getColour()), you could modify the toArray() method on your model to build the pen output manually.

class MyModel extends Model
{
    public function toArray()
    {
        $array = parent::toArray();

        if (!empty($this->pen)) {
            $array['pen'] = ['colour' => $this->pen->getColour()];
        }

        return $array;
    }
}

In the Model, toArray() is called by jsonSerialize(), which is called by the response when it json_encodes the data you pass into the json() method.

I managed to solve this by extending the model and using symphony/serializer component.

Below is the base model that has to be extended by all models needing this functionality.

Note that I have encoded then decoded it in the return line. The jsonSerialize() method - toJson() is the actual method that encodes it. However, modifying toJson() directly did not work as it is skipped over when using response()->json() in controller;

Also, I had to composer require symfony/property-access it's a dependency of ObjectNormalizer.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

class ProtectedSerializableModel extends Model
{
    public function jsonSerialize()
    {
        $serializer = (new Serializer([new ObjectNormalizer()], [new JsonEncoder()]));
        $attributes = $this->toArray();
        foreach($attributes as $k => &$v) {
            if(is_object($v)) {
                $v = get_object_vars($v);
            }
        }
        return json_decode($serializer->serialize($attributes, 'json'));
    }
}