0

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' }}

JC Lee
  • 2,337
  • 2
  • 18
  • 25
  • If you wanna get protected member, you need to add public method for getting $colour in Pen class. – Mirek Kowieski Jan 09 '17 at 05:50
  • As mentioned in the question the class is from a library I can't edit it. There is a getter method but that doesn't help. What I want is for JSON to output the protected member. – JC Lee Jan 09 '17 at 05:53
  • Possible duplicate of [Serializing PHP object to JSON](http://stackoverflow.com/questions/6836592/serializing-php-object-to-json) – Rafael Jan 09 '17 at 05:53
  • @Rafael I specifically mentioned that I cannot add JsonSerializable to my class as it's from a third party library. – JC Lee Jan 09 '17 at 05:55
  • @Rafael Its a library from Google. The classes are automatically generated so I doubt I will be allowed to overwrite them with my pull request. That's also outside the scope of the question. – JC Lee Jan 09 '17 at 06:00
  • Can you edit MyModel class? – Mirek Kowieski Jan 09 '17 at 06:01
  • @MirekKowieski Yes it's an Laravel eloquent model. – JC Lee Jan 09 '17 at 06:02
  • @JCLee So add `public $colour;` there and use a getter method from Pen class. `$myModel = new MyModel; $pen = new Pen; $myModel->pen = $pen->getColour();` – Mirek Kowieski Jan 09 '17 at 06:08
  • @MirekKowieski I need `$myModel->pen`'s type to be `Pen` as the library. As the code example in the question shows, If I were to change the type, I can no longer do `Library::pineapple($myModel->pen);` as the pineapple method has a type declaration: http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration – JC Lee Jan 09 '17 at 06:22

2 Answers2

0

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.

patricus
  • 59,488
  • 15
  • 143
  • 145
  • Yes, there are getters, but there are many classes like `Pen` and maintaining it will be problematic when the authors change the classes. So I am looking for a catch-all solution. Currently looking at [SymphonySerializer](http://symfony.com/doc/current/components/serializer.html) and extending the `Model` class's `jsonSerialize()` method. – JC Lee Jan 09 '17 at 07:12
  • @JCLee Sure. If you use that package, it seems like you'd just change the provided code from `$array['pen'] = ['colour' => $this->pen->getColour()];` to `$array['pen'] = $serializer->serialize($this->pen, 'json');` And, of course, you could do this in the `jsonSerialize` method if you want. It just depends on if you want this to affect any other times the Model may be converted to an array, for some reason. – patricus Jan 09 '17 at 07:30
  • @JCLee Does all getters have same name? (`getColour` in this case) – Cerlin Jan 09 '17 at 07:36
  • @CerlinBoss Yes, for reference you can click into any of the files [here](https://github.com/googleads/googleads-php-lib/tree/master/src/Google/AdsApi/AdWords/v201609/cm). I am close to a solution. – JC Lee Jan 09 '17 at 07:38
0

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'));
    }
}
JC Lee
  • 2,337
  • 2
  • 18
  • 25