1

I want to type hint this json structure in php:

{
  "settings": {
    "signup": {
      "logging": true,
      "forcePrompt": false,
      "completedSteps": [
        1,
        2,
        3
      ]
    },
    "trash": {
      "retentionDays": 30,
      "enabled": true
    }
  }
}

is there some way how I can do that with a single "Settings" class instead of having to define separate classes for every nested attribute (signup, trash).

in typescript I can just define at like this:

{
  settings: {
    signup: {
      logging: boolean,
      forcePrompt: boolean,
      completedSteps: number[]
    },
    trash: {
      retentionDays: number,
      enabled: boolean
    }
  }
}
Chris
  • 13,100
  • 23
  • 79
  • 162

1 Answers1

0

No, but you can roll your own and get pretty close. For example, I've a class that acts as a settings storage and extends ArrayObject for that, so it's pretty standard non-exotic stuff.

If you're fine with just checking the leaf attributes (e.g. "Is attribute X a boolean?" instead of "Is attribute X as a child of attribute Y a boolean"?), you could do a basic type checking like so (I've shortened the example code a bit, you'll find the full code here):


// Your data

$data = '{

  "settings": {
    "signup": {
      "logging": true,
      "forcePrompt": false,
      "completedSteps": [
        1,
        2,
        3
      ]
    },
    "trash": {
      "retentionDays": 30,
      "enabled": true
    }
  }
}
';

// We'll use this for type hinting

$defaults = '{
    
  "settings": {
    "signup": {
      "logging": true,
      "forcePrompt": false,
      "completedSteps": []
    },
    "trash": {
      "retentionDays": 1,
      "enabled": true
    }
  }
}
';


/**
 * Basic collection object.
 *
 * Usage:
 * ------
 * $collection->acme_social->twitter->client_id 
 */
class Collection extends \ArrayObject {

    /**
     * Initialize and turn all levels of the given
     * array into a collection object.
     * 
     * @param Array $data
     */
    public function __construct(array $data, array $defaults = []) {

        parent::__construct($data, \ArrayObject::ARRAY_AS_PROPS);

        // Turn all arrays into Collections

        $iterator = new \RecursiveIteratorIterator(

            new \RecursiveArrayIterator($this), \RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $key => $value) {
            
            // Evaluate types
                
            array_walk_recursive($defaults, function ($v, $k) use ($key, $value) {

                if ($key == $k) {

                    $expected = gettype($v);
                    $given    = gettype($value);

                    if ($expected != $given) {

                        throw new InvalidArgumentException("{$k} needs to be of type {$expected}, {$given} given.");
                    }
                }
            });

            if (is_array($value)) {

                $iterator->getInnerIterator()->offsetSet($key, new static($value));
            }
        }
    }
}

try {

    $collection = new Collection(json_decode($data, true), json_decode($defaults, true));
}
catch(Exception $e) {

    var_dump($e->getMessage());
}
nosurs
  • 680
  • 6
  • 13
  • Thanks for the suggestion. Unfortunately this will not provide typehints that can be used for code completion. – Chris Apr 05 '21 at 14:52