public static function create(array $params, bool $readonly = false) : OperationParams
    {
        $instance = new static();

        $params                  = array_change_key_case($params, CASE_LOWER);
        $instance->originalInput = $params;

        $params += [
            'query' => null,
            'queryid' => null,
            'documentid' => null, // alias to queryid
            'id' => null, // alias to queryid
            'operationname' => null,
            'variables' => null,
            'extensions' => null,
        ];

        if ($params['variables'] === '') {
            $params['variables'] = null;
        }

        // Some parameters could be provided as serialized JSON.
        foreach (['extensions', 'variables'] as $param) {
            if (! is_string($params[$param])) {
                continue;
            }

            $tmp = json_decode($params[$param], true);
            if (json_last_error()) {
                continue;
            }

            $params[$param] = $tmp;
        }

        $instance->query      = $params['query'];
        $instance->queryId    = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
        $instance->operation  = $params['operationname'];
        $instance->variables  = $params['variables'];
        $instance->extensions = $params['extensions'];
        $instance->readOnly   = $readonly;

        // Apollo server/client compatibility: look for the queryid in extensions
        if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
            $instance->queryId = $instance->extensions['persistedQuery']['sha256Hash'];
        }

        return $instance;
    }