mimmi20/browser-detector

View on GitHub
src/Loader/Helper/Data.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php
/**
 * This file is part of the browser-detector package.
 *
 * Copyright (c) 2012-2024, Thomas Mueller <mimmi20@live.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace BrowserDetector\Loader\Helper;

use FilterIterator;
use Iterator;
use JsonException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use SplFileInfo;
use stdClass;

use function array_key_exists;
use function assert;
use function count;
use function file_get_contents;
use function is_string;
use function json_decode;
use function sprintf;
use function str_replace;

use const JSON_THROW_ON_ERROR;

final class Data implements DataInterface
{
    /** @var array<string, stdClass> */
    private array $items      = [];
    private bool $initialized = false;

    /** @throws void */
    public function __construct(private readonly string $path, private readonly string $extension)
    {
        // nothing to do
    }

    /** @throws RuntimeException */
    public function __invoke(): void
    {
        if ($this->initialized) {
            return;
        }

        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->path));
        $files    = new class ($iterator, $this->extension) extends FilterIterator {
            /**
             * @param Iterator<SplFileInfo> $iterator
             *
             * @throws void
             */
            public function __construct(Iterator $iterator, private readonly string $extension)
            {
                parent::__construct($iterator);
            }

            /** @throws void */
            public function accept(): bool
            {
                $file = $this->getInnerIterator()->current();

                assert($file instanceof SplFileInfo);

                return $file->getExtension() === $this->extension;
            }
        };

        foreach ($files as $file) {
            assert($file instanceof SplFileInfo);

            $pathName = $file->getPathname();
            $filepath = str_replace('\\', '/', $pathName);
            assert(is_string($filepath));

            $content = @file_get_contents($filepath);

            if ($content === false) {
                throw new RuntimeException(sprintf('could not read file "%s"', $file));
            }

            try {
                $fileData = json_decode(json: $content, associative: false, flags: JSON_THROW_ON_ERROR);
            } catch (JsonException $e) {
                throw new RuntimeException(sprintf('file "%s" contains invalid json', $file), 0, $e);
            }

            assert($fileData instanceof stdClass);

            foreach ((array) $fileData as $key => $data) {
                $stringKey = (string) $key;

                if (array_key_exists($stringKey, $this->items) || !$data instanceof stdClass) {
                    continue;
                }

                $this->items[$stringKey] = $data;
            }
        }

        $this->initialized = true;
    }

    /** @throws void */
    public function getItem(string $cacheId): mixed
    {
        return $this->items[$cacheId] ?? null;
    }

    /** @throws void */
    public function hasItem(string $cacheId): bool
    {
        return array_key_exists($cacheId, $this->items);
    }

    /** @throws void */
    public function isInitialized(): bool
    {
        return $this->initialized;
    }

    /**
     * Count elements of an object
     *
     * @see https://php.net/manual/en/countable.count.php
     *
     * @return int the custom count as an integer
     *
     * @throws void
     */
    public function count(): int
    {
        return count($this->items);
    }
}