Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. JsonFile.php

class JsonFile

Reads/writes json files.

@author Konstantin Kudryashiv <ever.zet@gmail.com> @author Jordi Boggiano <j.boggiano@seld.be>

Hierarchy

  • class \Composer\Json\JsonFile

Expanded class hierarchy of JsonFile

35 files declare their use of JsonFile
ArchiveManager.php in vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php
ArtifactRepository.php in vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php
Auditor.php in vendor/composer/composer/src/Composer/Advisory/Auditor.php
AutoloadGenerator.php in vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php
BumpCommand.php in vendor/composer/composer/src/Composer/Command/BumpCommand.php

... See full list

File

vendor/composer/composer/src/Composer/Json/JsonFile.php, line 30

Namespace

Composer\Json
View source
class JsonFile {
    public const LAX_SCHEMA = 1;
    public const STRICT_SCHEMA = 2;
    public const AUTH_SCHEMA = 3;
    public const LOCK_SCHEMA = 4;
    
    /** @deprecated Use \JSON_UNESCAPED_SLASHES */
    public const JSON_UNESCAPED_SLASHES = 64;
    
    /** @deprecated Use \JSON_PRETTY_PRINT */
    public const JSON_PRETTY_PRINT = 128;
    
    /** @deprecated Use \JSON_UNESCAPED_UNICODE */
    public const JSON_UNESCAPED_UNICODE = 256;
    public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';
    public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json';
    public const INDENT_DEFAULT = '    ';
    
    /** @var string */
    private $path;
    
    /** @var ?HttpDownloader */
    private $httpDownloader;
    
    /** @var ?IOInterface */
    private $io;
    
    /** @var string */
    private $indent = self::INDENT_DEFAULT;
    
    /**
     * Initializes json file reader/parser.
     *
     * @param  string                    $path           path to a lockfile
     * @param  ?HttpDownloader           $httpDownloader required for loading http/https json files
     * @param  ?IOInterface              $io
     * @throws \InvalidArgumentException
     */
    public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null) {
        $this->path = $path;
        if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) {
            throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
        }
        $this->httpDownloader = $httpDownloader;
        $this->io = $io;
    }
    public function getPath() : string {
        return $this->path;
    }
    
    /**
     * Checks whether json file exists.
     */
    public function exists() : bool {
        return is_file($this->path);
    }
    
    /**
     * Reads json file.
     *
     * @throws ParsingException
     * @throws \RuntimeException
     * @return mixed
     */
    public function read() {
        try {
            if ($this->httpDownloader) {
                $json = $this->httpDownloader
                    ->get($this->path)
                    ->getBody();
            }
            else {
                if (!Filesystem::isReadable($this->path)) {
                    throw new \RuntimeException('The file "' . $this->path . '" is not readable.');
                }
                if ($this->io && $this->io
                    ->isDebug()) {
                    $realpathInfo = '';
                    $realpath = realpath($this->path);
                    if (false !== $realpath && $realpath !== $this->path) {
                        $realpathInfo = ' (' . $realpath . ')';
                    }
                    $this->io
                        ->writeError('Reading ' . $this->path . $realpathInfo);
                }
                $json = file_get_contents($this->path);
            }
        } catch (TransportException $e) {
            throw new \RuntimeException($e->getMessage(), 0, $e);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not read ' . $this->path . "\n\n" . $e->getMessage());
        }
        if ($json === false) {
            throw new \RuntimeException('Could not read ' . $this->path);
        }
        $this->indent = self::detectIndenting($json);
        return static::parseJson($json, $this->path);
    }
    
    /**
     * Writes json file.
     *
     * @param  mixed[]                              $hash    writes hash into json file
     * @param  int                                  $options json_encode options
     * @throws \UnexpectedValueException|\Exception
     * @return void
     */
    public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) {
        if ($this->path === 'php://memory') {
            file_put_contents($this->path, static::encode($hash, $options, $this->indent));
            return;
        }
        $dir = dirname($this->path);
        if (!is_dir($dir)) {
            if (file_exists($dir)) {
                throw new \UnexpectedValueException(realpath($dir) . ' exists and is not a directory.');
            }
            if (!@mkdir($dir, 0777, true)) {
                throw new \UnexpectedValueException($dir . ' does not exist and could not be created.');
            }
        }
        $retries = 3;
        while ($retries--) {
            try {
                $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent) . ($options & JSON_PRETTY_PRINT ? "\n" : ''));
                break;
            } catch (\Exception $e) {
                if ($retries > 0) {
                    usleep(500000);
                    continue;
                }
                throw $e;
            }
        }
    }
    
    /**
     * Modify file properties only if content modified
     *
     * @return int|false
     */
    private function filePutContentsIfModified(string $path, string $content) {
        $currentContent = @file_get_contents($path);
        if (false === $currentContent || $currentContent !== $content) {
            return file_put_contents($path, $content);
        }
        return 0;
    }
    
    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @throws ParsingException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null) : bool {
        if (!Filesystem::isReadable($this->path)) {
            throw new \RuntimeException('The file "' . $this->path . '" is not readable.');
        }
        $content = file_get_contents($this->path);
        $data = json_decode($content);
        if (null === $data && 'null' !== $content) {
            self::validateSyntax($content, $this->path);
        }
        return self::validateJsonSchema($this->path, $data, $schema, $schemaFile);
    }
    
    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  mixed                   $data       Decoded JSON data to validate
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null) : bool {
        $isComposerSchemaFile = false;
        if (null === $schemaFile) {
            if ($schema === self::LOCK_SCHEMA) {
                $schemaFile = self::LOCK_SCHEMA_PATH;
            }
            else {
                $isComposerSchemaFile = true;
                $schemaFile = self::COMPOSER_SCHEMA_PATH;
            }
        }
        // Prepend with file:// only when not using a special schema already (e.g. in the phar)
        if (false === strpos($schemaFile, '://')) {
            $schemaFile = 'file://' . $schemaFile;
        }
        $schemaData = (object) [
            '$ref' => $schemaFile,
        ];
        if ($schema === self::LAX_SCHEMA) {
            $schemaData->additionalProperties = true;
            $schemaData->required = [];
        }
        elseif ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) {
            $schemaData->additionalProperties = false;
            $schemaData->required = [
                'name',
                'description',
            ];
        }
        elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) {
            $schemaData = (object) [
                '$ref' => $schemaFile . '#/properties/config',
                '$schema' => "https://json-schema.org/draft-04/schema#",
            ];
        }
        $validator = new Validator();
        $validator->check($data, $schemaData);
        if (!$validator->isValid()) {
            $errors = [];
            foreach ((array) $validator->getErrors() as $error) {
                $errors[] = ($error['property'] ? $error['property'] . ' : ' : '') . $error['message'];
            }
            throw new JsonValidationException('"' . $source . '" does not match the expected JSON schema', $errors);
        }
        return true;
    }
    
    /**
     * Encodes an array into (optionally pretty-printed) JSON
     *
     * @param  mixed  $data    Data to encode into a formatted JSON string
     * @param  int    $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @param  string $indent  Indentation string
     * @return string Encoded json
     */
    public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT) : string {
        $json = json_encode($data, $options);
        if (false === $json) {
            self::throwEncodeError(json_last_error());
        }
        if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT) {
            // Pretty printing and not using default indentation
            return Preg::replaceCallback('#^ {4,}#m', static function ($match) use ($indent) : string {
                return str_repeat($indent, (int) (strlen($match[0]) / 4));
            }, $json);
        }
        return $json;
    }
    
    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @throws \RuntimeException
     * @return never
     */
    private static function throwEncodeError(int $code) : void {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }
        throw new \RuntimeException('JSON encoding failed: ' . $msg);
    }
    
    /**
     * Parses json string and returns hash.
     *
     * @param null|string $json json string
     * @param string $file the json file
     *
     * @throws ParsingException
     * @return mixed
     */
    public static function parseJson(?string $json, ?string $file = null) {
        if (null === $json) {
            return null;
        }
        $data = json_decode($json, true);
        if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
            self::validateSyntax($json, $file);
        }
        return $data;
    }
    
    /**
     * Validates the syntax of a JSON string
     *
     * @param  string                    $file
     * @throws \UnexpectedValueException
     * @throws ParsingException
     * @return bool                      true on success
     */
    protected static function validateSyntax(string $json, ?string $file = null) : bool {
        $parser = new JsonParser();
        $result = $parser->lint($json);
        if (null === $result) {
            if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
                if ($file === null) {
                    throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON');
                }
                else {
                    throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON');
                }
            }
            return true;
        }
        if ($file === null) {
            throw new ParsingException('The input does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails());
        }
        else {
            throw new ParsingException('"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(), $result->getDetails());
        }
    }
    public static function detectIndenting(?string $json) : string {
        if (Preg::isMatchStrictGroups('#^([ \\t]+)"#m', $json ?? '', $match)) {
            return $match[1];
        }
        return self::INDENT_DEFAULT;
    }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary
JsonFile::$httpDownloader private property @var ?HttpDownloader
JsonFile::$indent private property @var string
JsonFile::$io private property @var ?IOInterface
JsonFile::$path private property @var string
JsonFile::AUTH_SCHEMA public constant
JsonFile::COMPOSER_SCHEMA_PATH public constant
JsonFile::detectIndenting public static function
JsonFile::encode public static function Encodes an array into (optionally pretty-printed) JSON
JsonFile::exists public function Checks whether json file exists.
JsonFile::filePutContentsIfModified private function Modify file properties only if content modified
JsonFile::getPath public function
JsonFile::INDENT_DEFAULT public constant
JsonFile::JSON_PRETTY_PRINT Deprecated public constant
JsonFile::JSON_UNESCAPED_SLASHES Deprecated public constant
JsonFile::JSON_UNESCAPED_UNICODE Deprecated public constant
JsonFile::LAX_SCHEMA public constant
JsonFile::LOCK_SCHEMA public constant
JsonFile::LOCK_SCHEMA_PATH public constant
JsonFile::parseJson public static function Parses json string and returns hash.
JsonFile::read public function Reads json file.
JsonFile::STRICT_SCHEMA public constant
JsonFile::throwEncodeError private static function Throws an exception according to a given code with a customized message
JsonFile::validateJsonSchema public static function Validates the schema of the current json file according to composer-schema.json rules
JsonFile::validateSchema public function Validates the schema of the current json file according to composer-schema.json rules
JsonFile::validateSyntax protected static function Validates the syntax of a JSON string
JsonFile::write public function Writes json file.
JsonFile::__construct public function Initializes json file reader/parser.

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal