int); type DBResult = shape( 'id' => int, 'name' => string, 'extra' => DBResultExtra, ); final class FakeDB { public function getRawRows(): array> { $good_extra = json_encode(array('age' => 40)); $bad_extra = 'corrupt data'; // Real code would query a DB, but for now let's hardcode it return array( array( 'id' => 123, 'name' => 'Alice', 'extra' => $good_extra, ), array( 'id' => 456, 'name' => 'Bob', 'extra' => $bad_extra, ), ); } /** * When processing untyped data you need to check each piece of data and * figure out whether to give up or recover when the data is bad */ public function processRow(array $row): ?DBResult { $row = Map::fromArray($row); $id = $row->contains('id') ? $row['id'] : null; $name = $row->contains('name') ? $row['name'] : null; $extra = $row->contains('extra') ? json_decode($row['extra'], true) : null; // Ignore rows with invalid IDs or names if (!is_int($id) || !is_string($name)) { return null; } // Try to recover from a bad extra column if (!is_array($extra)) { $extra = shape('age' => 0); } else { $extra = Map::fromArray($extra); $extra = shape('age' => $extra->contains('age') ? $extra['age'] : 0); } return shape('id' => $id, 'name' => $name, 'extra' => $extra); } public function getDBResults(): Vector { $ret = Vector {}; foreach ($this->getRawRows() as $raw_row) { $row = $this->processRow($raw_row); if ($row !== null) { $ret->add($row); } } return $ret; } }