json-schema-php_PHP_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > PHP > json-schema-php

json-schema-php

 2010/12/25 0:03:00  haohappy2  http://wuchengyi.javaeye.com  我要评论(0)
  • 摘要:?php/**MoXie(SysTem128@GMail.Com)2010-12-11**Copyright©2010-2010Zoeey.Org*Codelicense:GNULesserGeneralPublicLicenseVersion3*http://www.gnu.org/licenses/lgpl-3.0.txt**website:*http://code.google.com/p/json-schema
  • 标签:PHP JSON Schema

?php

/*
 * MoXie (SysTem128@GMail.Com) 2010-12-11 
 *
 * Copyright © 2010-2010 Zoeey.Org
 * Code license: GNU Lesser General Public License Version 3
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * website:
 * http://code.google.com/p/json-schema-php/
 */

/**
 * JSON Schema generate/validate
 * 
 * @author moxie(system128@gmail.com)
 */
class JsonSchema {

    /**
     * JSON
     *
     * @var string
     */
    private $json;
    /**
     * Last error
     * 
     * @var array
     */
    private $errors;
    /**
     * Extend types
     *
     * @var map
     */
    private $complexTypes;

    /**
     *
     * @param string $json
     */
    function __construct($json) {
        $this->errors = array();
        $this->complexTypes = array();
        $this->json = json_decode($json);
    }

    /**
     * Generate JSON Schema
     *
     * @return string JSON Schema
     */
    public function getSchema() {
        $schema = null;
        $schema = $this->genByType($this->json);
        return json_encode($schema);
    }

    /**
     * Generate JSON Schema by type
     * @param mixed $value
     * @return object
     */
    private function genByType($value) {
        $type = gettype($value);
        $schema = array();
        switch ($type) {
            case 'boolean':
                $schema['type'] = 'boolean';
                $schema['default'] = false;
                break;
            case 'integer':
                $schema['type'] = 'integer';
                $schema['default'] = 0;
                $schema['minimum'] = 0;
                $schema['maximum'] = PHP_INT_MAX;
                $schema['exclusiveMinimum'] = 0;
                $schema['exclusiveMaximum'] = PHP_INT_MAX;
                break;
            case 'double':
                $schema['type'] = 'number';
                $schema['default'] = 0;
                $schema['minimum'] = 0;
                $schema['maximum'] = PHP_INT_MAX;
                $schema['exclusiveMinimum'] = 0;
                $schema['exclusiveMaximum'] = PHP_INT_MAX;
                break;
            case 'string':
                $schema['type'] = 'string';
                $schema['format'] = 'regex';
                $schema['pattern'] = '/^[a-z0-9]+$/i';
                $schema['minLength'] = 0;
                $schema['maxLength'] = PHP_INT_MAX;
                break;
            case 'array':
                $schema['type'] = 'array';
                $schema['minItems'] = 0;
                $schema['maxItems'] = 20;
                $items = array();
                foreach ($value as $value) {
                    $items = $this->genByType($value);
                    break;
                }
                $schema['items'] = $items;
                break;
            case 'object':
                $schema['type'] = 'object';
                $items = array();
                $value = get_object_vars($value);
                foreach ($value as $key => $value) {
                    $items[$key] = $this->genByType($value);
                }
                $schema['properties'] = $items;
                break;
            case 'null': // any in union types
                $schema['type'] = 'null';
                break;
            default:
                break;
        }
        return $schema;
    }

    /**
     * Set type schema
     * @param string $typeSchema
     */
    public function addType($typeSchema) {
        if (empty($typeSchema)) {
            return;
        }
        $typeSchema = json_decode($typeSchema, true);
        if (is_array($typeSchema) && isset($typeSchema['id'])) {
            $this->complexTypes[$typeSchema['id']] = $typeSchema;
        }
    }

    /**
     * Get type schema
     *
     * @param string ref
     * @return string schema
     */
    private function getType($ref) {
        if (isset($this->complexTypes[$ref])) {
            return $this->complexTypes[$ref];
        }
        return null;
    }

    /**
     * Validate JSON
     *
     * @param string $schema JSON Schema
     * @return boolean
     */
    public function validate($schema) {
        $isVali = false;
        do {
            $schema = json_decode($schema, true);
            if (!is_array($schema) || !isset($schema['type'])) {
                $this->addError('schema parse error. (PHP 5 >= 5.3.0) see json_last_error(void).');
                break;
            }
            $isVali = $this->checkByType($this->json, $schema);
        } while (false);
        return $isVali;
    }

    /**
     * check type: string
     * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
     * 
     * @param string $value
     * @param array $schema
     */
    private function checkString($value, $schema) {
        // string
        $isVali = false;
        do {

            if (!is_string($value)) {
                $this->addError(sprintf('value: "%s" is not a string.', $value));
                break;
            }
            $len = strlen($value);
            if (isset($schema['minLength'])) {
                if ($schema['minLength'] > $len) {
                    $this->addError(sprintf('value: "%s" is too short.', $value));
                    break;
                }
            }
            if (isset($schema['maxLength'])) {
                if ($schema['maxLength'] < $len) {
                    $this->addError(sprintf('value: "%s" is too long.', $value));
                    break;
                }
            }

            if (isset($schema['format'])) {
                switch ($schema['format']) {

                    case 'date-time':
                        /**
                         * date-time  This SHOULD be a date in ISO 8601 format of YYYY-MM-
                         * DDThh:mm:ssZ in UTC time.  This is the recommended form of date/
                         * timestamp.
                         */
                        break;
                    case 'date':
                        /**
                         * date  This SHOULD be a date in the format of YYYY-MM-DD.  It is
                         * recommended that you use the "date-time" format instead of "date"
                         * unless you need to transfer only the date part.
                         */
                        break;
                    case 'time':
                        /**
                         * time  This SHOULD be a time in the format of hh:mm:ss.  It is
                         * recommended that you use the "date-time" format instead of "time"
                         * unless you need to transfer only the time part.
                         */
                        break;
                    case 'utc-millisec':
                        /**
                         * utc-millisec  This SHOULD be the difference, measured in
                         * milliseconds, between the specified time and midnight, 00:00 of
                         * January 1, 1970 UTC.  The value SHOULD be a number (integer or
                         * float).
                         */
                        break;
                    case 'regex':
                        /**
                         * regex  A regular expression, following the regular expression
                         * specification from ECMA 262/Perl 5.
                         */
                        if (isset($schema['pattern'])) {
                            $pattern = $schema['pattern'];
                            if (preg_match($pattern, $value)) {
                                $isVali = true;
                            } else {
                                $this->addError(printf('"%s" does not match "%s"', $value, $pattern));
                            }
                        } else {
                            $this->addError('format-regex: pattern is undefined.');
                        }

                        break;
                    case 'color':
                        /**
                         * color  This is a CSS color (like "#FF0000" or "red"), based on CSS
                         * 2.1 [W3C.CR-CSS21-20070719].
                         */
                        break;
                    case 'style':
                        /**
                         * style  This is a CSS style definition (like "color: red; background-
                         * color:#FFF"), based on CSS 2.1 [W3C.CR-CSS21-20070719].
                         */
                        break;
                    case 'phone':
                        /**
                         * phone  This SHOULD be a phone number (format MAY follow E.123).
                         * http://en.wikipedia.org/wiki/E.123
                         */
                        if (preg_match("/^(\(0?[0-9]{2}\) \d{3,4}\s?\d{4}|\+\d{2} \d{2} \d{3,4}\s?\d{4})$/", $value)) {
                            $isVali = true;
                        } else {
                            $this->addError(sprintf('value: "%s" is not a phone number.', $value));
                        }
                        break;
                    case 'uri':
                        /**
                         * uri  This value SHOULD be a URI..
                         */
                        if (filter_var($value, FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED)) {
                            $isVali = true;
                        } else {
                            $this->addError(sprintf('value: "%s" is not a URI.', $value));
                        }
                        break;
                    case 'email':
                        /**
                         *  email  This SHOULD be an email address.
                         */
                        if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
                            $isVali = true;
                        } else {
                            $this->addError(sprintf('value: "%s" is not a email.', $value));
                        }
                        break;
                    case 'ip-address':
                        /**
                         *  ip-address  This SHOULD be an ip version 4 address.
                         */
                        if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                            $isVali = true;
                        } else {
                            $this->addError(sprintf('value: "%s" is not a ipv4 address.', $value));
                        }

                        break;
                    case 'ipv6':
                        /**
                         *  ipv6  This SHOULD be an ip version 6 address.
                         */
                        if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
                            $isVali = true;
                        } else {
                            $this->addError(sprintf('value: "%s" is not a ipv6 address.', $value));
                        }
                        break;
                    case 'host-name':
                        /**
                         *  host-name  This SHOULD be a host-name.
                         */
                        break;

                    default:
                        $this->addError(sprintf('format: "%s" is undefined.', $schema['format']));
                        break;
                }
                break;
            }

            $isVali = true;
        } while (false);
        return $isVali;
    }

    /**
     * check type: integer/double
     *
     * @param number $value
     * @param array $schema
     * @return boolean
     */
    private function checkNumber($value, $schema) {
        // number
        $isVali = false;
        do {

            if (!is_numeric($value)) {
                $this->addError($value . ' is not a number.');
                break;
            }
            if (isset($schema['minimum'])) {
                if ($schema['minimum'] > $value) {
                    $this->addError(sprintf('%s is less than %s.', $value, $schema['minimum']));
                    break;
                }
            }
            if (isset($schema['maximum'])) {
                if ($schema['maximum'] < $value) {
                    $this->addError(sprintf('%s is bigger than %s.', $value, $schema['maximum']));
                    break;
                }
            }
            if (isset($schema['exclusiveMinimum'])) {
                if ($schema['exclusiveMinimum'] >= $value) {
                    $this->addError(sprintf('%s is less or than %s or equal.', $value, $schema['exclusiveMinimum']));
                    break;
                }
            }
            if (isset($schema['exclusiveMaximum'])) {
                if ($schema['exclusiveMaximum'] <= $value) {
                    $this->addError(sprintf('%s is bigger than %s or equal.', $value, $schema['exclusiveMaximum']));
                    break;
                }
            }
            $isVali = true;
        } while (false);

        return $isVali;
    }

    /**
     * check type: integer
     *
     * @param integer $value
     * @param array $schema
     * @return boolean
     */
    private function checkInteger($value, $schema) {
        // integer
        if (!is_integer($value)) {
            $this->addError(sprintf('value:"%s" is not a integer.', $value));
            return false;
        }
        return $this->checkNumber($value, $schema);
    }

    /**
     * check type: boolean
     *
     * @param boolean $value
     * @param array $schema
     * @return boolean
     */
    private function checkBoolean($value, $schema) {
        // boolean
        if (!is_bool($value)) {
            $this->addError(sprintf('value: "%s" is not a boolean.', $value));
            return false;
        }
        return true;
    }

    /**
     * check type: object
     *
     * @param object $valueProp
     * @param array $schema
     * @return boolean
     */
    private function checkObject($value, $schema) {
        // object
        $isVali = false;
        do {
            if (!is_object($value)) {
                $this->addError(sprintf('value: "%s" is not an object.', $value));
                break;
            }
            if (isset($schema['properties'])
                    && !empty($schema['properties'])
            ) {
                $schemaProp = $schema['properties'];
                $valueProp = get_object_vars($value);
                $valueKeys = array_keys($valueProp);
                $schemaKeys = array_keys($schemaProp);
                $diffKeys = array_diff($valueKeys, $schemaKeys);
                if (!empty($diffKeys)) {
                    foreach ($diffKeys as $key) {
                        // property not defined / not optional
                        if (!isset($schemaProp[$key])
                                || !isset($schemaProp[$key]['optional'])
                                || !$schemaProp[$key]['optional']
                        ) {
                            $this->addError(sprintf('key: "%s" is not exist,And it\'s not a optional property.', $value));
                            break 2;
                        }
                    }
                }
                foreach ($schemaProp as $key => $sch) {
                    if (!isset($valueProp[$key])) {
                        continue;
                    }
                    if (!$this->checkByType($valueProp[$key], $sch)) {
                        break 2;
                    }
                }
            }
            $isVali = true;
        } while (false);
        return $isVali;
    }

    /**
     * check type: array
     *
     * @param array $value
     * @param array $schema
     * @return boolean 
     */
    private function checkArray($value, $schema) {
        $isVali = false;
        do {
            if (!is_array($value)) {
                $this->addError(sprintf('value: "%s" is not an array.', $value));
                break;
            }

            if (!isset($schema['items'])) {
                $this->addError('schema: items schema is undefined.');
                break;
            }
            $size = count($value);
            if (isset($schema['minItems'])) {
                if ($schema['minItems'] > $size) {
                    $this->addError(sprintf('array size: %s  is less than %s.', $size, $schema['minItems']));
                    break;
                }
            }
            if (isset($schema['maxItems'])) {
                if ($schema['maxItems'] < $size) {
                    $this->addError(sprintf('array size: %s is bigger than %s.', $size, $schema['maxItems']));
                    break;
                }
            }

            foreach ($value as $val) {
                if (!$this->checkByType($val, $schema['items'])) {
                    break 2;
                }
            }


            $isVali = true;
        } while (false);
        return $isVali;
    }

    /**
     * check value based on type
     *
     * @param mixed $value
     * @param array $schema
     * @return boolean
     */
    private function checkByType($value, $schema) {
        $isVali = false;
        if ($schema && isset($schema['type'])) {
            // union types
            if (is_array($schema['type'])) {
                $types = $schema['type'];
                foreach ($types as $type) {
                    $schema['type'] = $type;
                    $isVali = $this->checkByType($value, $schema);
                    if ($isVali) {
                        break;
                    }
                }
            } else {
                $type = $schema['type'];
                switch ($type) {
                    case 'boolean':
                        $isVali = $this->checkBoolean($value, $schema);
                        break;
                    case 'integer':
                        $isVali = $this->checkInteger($value, $schema);
                        break;
                    case 'number':
                        $isVali = $this->checkNumber($value, $schema);
                        break;
                    case 'string':
                        $isVali = $this->checkString($value, $schema);
                        break;
                    case 'array':
                        $isVali = $this->checkArray($value, $schema);
                        break;
                    case 'object':
                        $isVali = $this->checkObject($value, $schema);
                        break;
                    case 'enum':
                        $isVali = is_null($value);
                        break;
                    case 'null':
                        $isVali = is_null($value);
                        break;
                    case 'any':
                        $isVali = true;
                        break;
                    default:
                        $this->addError(sprintf('type_schema: "%s" is undefined.', $value));
                        break;
                }
            }
        }
        if (isset($schema['$ref'])) {
            $isVali = $this->checkByType($value, $this->getType($schema['$ref']));
        }
        return $isVali;
    }

    /**
     *  Get errors
     *
     * @return array errors
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * add error message
     * @param string $msg
     */
    protected function addError($msg) {
        $this->errors[] = htmlentities($msg);
    }

}

?>
?
发表评论
用户名: 匿名