[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{md,rst}]\ntrim_trailing_whitespace = false\n\n[*.yml]\nindent_size = 2\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: '0 4 * * *'\n\njobs:\n  tests:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n\n    name: PHP 8.3\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: 8.3\n          tools: composer\n          coverage: xdebug\n\n      - name: Install dependencies\n        run:\n          composer update\n\n      - name: Composer normalize\n        run:\n          composer normalize --dry-run --indent-size=4 --indent-style=space\n\n      - name: Coding Standard\n        run:\n          vendor/bin/php-cs-fixer fix --diff --dry-run --verbose\n\n      - name: PHPMD\n        run:\n          vendor/bin/phpmd app xml phpmd.xml\n\n      - name: PHPStan\n        run:\n          vendor/bin/phpstan analyse -vvv\n\n      - name: PHPUnit\n        run: vendor/bin/phpunit\n\n      - name: Upload coverage results to Coveralls\n        env:\n          COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          mkdir -p build/logs/\n          cp build/coverage/clover.xml build/logs/clover.xml\n          composer global require php-coveralls/php-coveralls\n          php-coveralls --coverage_clover=build/logs/clover.xml -v\n\n  tests-latest:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n\n    name: PHP Latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: latest\n          tools: composer\n          coverage: xdebug\n\n      - name: Install dependencies\n        run:\n          composer update\n\n      - name: PHPUnit\n        run: vendor/bin/phpunit\n"
  },
  {
    "path": ".gitignore",
    "content": ".config.php\n.env\n.env.php\n.idea\n.php-cs-fixer.cache\n.phpunit.cache\n.phpunit.result.cache\n.vscode\nbuild\ncomposer.lock\ncomposer.phar\nphpstan.neon\nphpunit.xml\nraw-tests\nvendor\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "---\nimage: registry.gitlab.com/aplus-framework/images/lempa:4\n\ninclude:\n  - template: Security/SAST.gitlab-ci.yml\n\nvariables:\n  SAST_EXCLUDED_PATHS: guide, tests, vendor\n\ntest:php:\n  stage: test\n  timeout: 30 minutes\n  cache:\n    paths:\n      - vendor/\n  before_script:\n    - php -v\n    - composer update\n  script:\n    - composer normalize --dry-run --indent-size=4 --indent-style=space\n    - vendor/bin/php-cs-fixer fix --diff --dry-run --verbose\n    - vendor/bin/phpmd app xml phpmd.xml\n    - vendor/bin/phpstan analyse -vvv\n    - vendor/bin/phpunit --colors=never\n    - phpdoc\n  artifacts:\n    paths:\n      - build/coverage/\n      - build/docs/\n  coverage: '/^\\s*Lines:\\s*\\d+.\\d+\\%/'\n\ntest:php-latest:\n  image: registry.gitlab.com/aplus-framework/images/lempa:latest\n  stage: test\n  timeout: 30 minutes\n  cache:\n    paths:\n      - vendor/\n  before_script:\n    - php -v\n    - composer update\n  script:\n    - vendor/bin/phpunit --colors=never\n  coverage: '/^\\s*Lines:\\s*\\d+.\\d+\\%/'\n\npages:\n  stage: deploy\n  timeout: 10 minutes\n  dependencies:\n    - test:php\n  environment:\n    name: production\n    url: https://aplus-framework.gitlab.io\n  script:\n    - mv public/ app-public/\n    - mkdir public/\n    - mv build/coverage/ public/\n    - mv build/docs/ public/\n  artifacts:\n    paths:\n      - public/\n  only:\n    - master\n"
  },
  {
    "path": ".htaccess",
    "content": "Options All -Indexes\n\n<IfModule mod_rewrite.c>\n    RewriteEngine On\n\n    # Redirect to the public directory\n    RewriteRule ^$ public/ [L]\n    RewriteRule (.*) public/$1 [L]\n</IfModule>\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nuse Framework\\CodingStandard\\Config;\nuse Framework\\CodingStandard\\Finder;\n\nreturn (new Config())->setDefaultHeaderComment(\n    'App Project',\n    '' // [fullname] [<email>]\n)->setFinder(\n    Finder::create()->in(__DIR__)->exclude('storage')\n);\n"
  },
  {
    "path": "App.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Class App.\n *\n * @package app\n *\n * @see https://docs.aplus-framework.com/guides/projects/app/#the-global-class-app\n */\nclass App extends Framework\\MVC\\App\n{\n}\n"
  },
  {
    "path": "README.md",
    "content": "<a href=\"https://github.com/aplus-framework/app\"><img src=\"https://raw.githubusercontent.com/aplus-framework/app/master/guide/image.png\" alt=\"Aplus Framework App Project\" align=\"right\" width=\"100\"></a>\n\n# Aplus Framework App Project\n\n- [Home](https://aplus-framework.com/packages/app)\n- [User Guide](https://docs.aplus-framework.com/guides/projects/app/index.html)\n- [API Documentation](https://docs.aplus-framework.com/packages/app.html)\n- [Online Demo](https://demo.aplus-framework.com)\n\n[![tests](https://github.com/aplus-framework/app/actions/workflows/tests.yml/badge.svg)](https://github.com/aplus-framework/app/actions/workflows/tests.yml)\n[![coverage](https://coveralls.io/repos/github/aplus-framework/app/badge.svg?branch=master)](https://coveralls.io/github/aplus-framework/app?branch=master)\n[![packagist](https://img.shields.io/packagist/v/aplus/app)](https://packagist.org/packages/aplus/app)\n[![open-source](https://img.shields.io/badge/open--source-sponsor-magenta)](https://aplus-framework.com/sponsor)\n\n## Getting Started\n\nMake sure you have [Composer](https://getcomposer.org/doc/00-intro.md) installed.\n\nFollow the installation instructions in the [User Guide](https://docs.aplus-framework.com/guides/projects/app/index.html).\n\nTo install the latest version:\n\n```\ncomposer create-project aplus/app\n```\n\nOr, to install the latest [LTS](https://aplus-framework.com/lts) version:\n\n```\ncomposer create-project aplus/app:^24\n```\n\nEnter the project directory.\n\n---\n\nOptionally, you can start a new project on GitHub from [this template](https://github.com/new?template_name=app&template_owner=aplus-framework).\n\n## Licensing\n\nAdd a `LICENSE` file.\n\nIf you think about open-source your project,\n[choose a license](https://choosealicense.com/licenses/).\n\nIf your project is proprietary, you can add your custom license or\n[not](https://choosealicense.com/no-permission/).\n\nEdit the `.php-cs-fixer.dist.php` file.\nSet the project name and copyright information.\n\nTo update the comment header in all PHP files, run:\n\n```\nvendor/bin/php-cs-fixer fix -vvv\n```\n\n## Code Quality\n\nAplus Framework uses Code Quality Tools in all its projects.\n\nBy default, App Project also uses the following tools as dev-dependencies:\n\n- [PHP-CS-Fixer](https://cs.symfony.com)\n- [phpDocumentor](https://phpdoc.org)\n- [PHPMD](https://phpmd.org)\n- [PHPStan](https://phpstan.org)\n- [PHPUnit](https://phpunit.de)\n\n### Static Analysis\n\nYou can find bugs in your code without writing tests by running:\n\n```\nvendor/bin/phpstan analyse\n```\n\nSee the `phpstan.neon.dist` file for more details.\n\n### Mess Detector\n\nYou can look for several potential problems in the source code by running:\n\n```\nvendor/bin/phpmd app xml phpmd.xml\n```\n\nCustomize your rules in the `phpmd.xml` file.\n\n### Coding Standard\n\nWe extend PHP-CS-Fixer to create the\n[Coding Standard Library](https://github.com/aplus-framework/coding-standard).\n\nIt is [PSR-12](https://www.php-fig.org/psr/psr-12/) compatible.\n\nYou can see what to fix in the source code by running:\n\n```\nvendor/bin/php-cs-fixer fix --diff --dry-run --verbose\n```\n\n### Testing\n\nWe extend PHPUnit to create the\n[Testing Library](https://github.com/aplus-framework/testing).\n\nYou can unit test your code by running:\n\n```\nvendor/bin/phpunit\n```\n\nSee the `phpunit.xml.dist` file for more details.\n\n### Documenting\n\nGood software usually has good documentation.\n\nYou can build beautiful HTML pages about your project's documentation.\n\nYou must have phpDocumentor installed on your computer or run `phpdoc`\n[inside a container](#containers).\n\n## Development Environment\n\nThe App Project is delivered with a dev-dependency to easily configure the\nbuilt-in PHP development server.\n\nJust run\n\n```\nvendor/bin/php-server\n```\n\nand your project will be available at http://localhost:8080.\n\nSee the `php-server.ini` file for more details.\n\n### Containers\n\nAplus has Docker [images](https://gitlab.com/aplus-framework/images) for testing\nand building software.\n\nYou can run it in CI or local environments.\n\nWith [Docker](https://www.docker.com/get-started) installed on your computer,\nyou can run:\n\n```\ndocker-compose run --service-ports lempa\n```\n\nThis will log you as the **developer** user into a Docker container where you can\nrun all your tests.\n\nBy default, the web app will be available at http://localhost, on ports 80 and 443.\n\nSee the `docker-compose.yml` file for more details.\n\n## Continuous Integration\n\nApp Project is cross-platform and can be used in public and private projects.\n\nYou can use it on [GitLab](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/),\non [GitHub](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration),\non your computer, anywhere you want.\n\nThe App Project is already pre-configured to run in a GitLab CI environment.\n\nSee the `.gitlab-ci.yml` file for more details.\n\nJust upload your project to GitLab and it will run\n[pipelines](https://docs.gitlab.com/ee/ci/pipelines/#view-pipelines).\n\nOn GitHub, it will run [workflows](https://docs.github.com/en/actions) to test\nyour code every Push or Pull Request.\n\nCheck the `.github` folder to see more.\n\n## And now?\n\nGo build an API or a website, an awesome app! ⚡\n\nSee you.\n\n---\n\nIf you have a little time...\n\nVisit the Aplus Framework website: [aplus-framework.com](https://aplus-framework.com)\n\nFollow Aplus on:\n\n- [GitHub](https://github.com/aplus-framework)\n- [X](https://x.com/AplusFramework)\n- [Facebook](https://www.facebook.com/AplusFramework)\n- [YouTube](https://www.youtube.com/@AplusFramework)\n\nStay tuned for our updates.\n\nShare your experiences about meet us!\n\n**Remember**:\n\n> Coding is Art.\n>\n> Coding is Engineering.\n>\n> Good developer loves to code.\n>\n> **Code with Love!**\n\n---\n\nThe Aplus Framework Team\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nThe **latest** and **LTS** versions of this library receive security fixes.\n\nIf you find any vulnerability send an email to `aplusframework@gmail.com`.\n"
  },
  {
    "path": "app/Commands/Index.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace App\\Commands;\n\nuse Framework\\CLI\\CLI;\n\n/**\n * Class Index.\n *\n * @package app\n */\nclass Index extends \\Framework\\CLI\\Commands\\Index\n{\n    protected function showHeader() : void\n    {\n        $banner = <<<'EOL'\n                _          _                _\n               / \\   _ __ | |_   _ ___     / \\   _ __  _ __\n              / _ \\ | '_ \\| | | | / __|   / _ \\ | '_ \\| '_ \\\n             / ___ \\| |_) | | |_| \\__ \\  / ___ \\| |_) | |_) |\n            /_/   \\_\\ .__/|_|\\__,_|___/ /_/   \\_\\ .__/| .__/\n                    |_|                         |_|   |_|\n\n            EOL;\n        CLI::write($banner, 'green');\n    }\n}\n"
  },
  {
    "path": "app/Controllers/Home.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace App\\Controllers;\n\nuse Framework\\MVC\\Controller;\nuse Framework\\Routing\\Attributes\\Route;\n\n/**\n * Class Home.\n *\n * @package app\n */\nfinal class Home extends Controller\n{\n    /**\n     * Renders the application homepage.\n     */\n    #[Route('GET', '/', name: 'home.index')]\n    public function index() : string\n    {\n        return view('home/index');\n    }\n}\n"
  },
  {
    "path": "app/Languages/en/home.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nreturn [\n    'description' => 'The app is running! Yep!',\n    'title' => 'Aplus Framework',\n];\n"
  },
  {
    "path": "app/Languages/es/home.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nreturn [\n    'description' => '¡La app se está ejecutando! ¡Sí!',\n    'title' => 'Aplus Framework',\n];\n"
  },
  {
    "path": "app/Languages/pt-br/home.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nreturn [\n    'description' => 'O app está rodando! Sim!',\n    'title' => 'Aplus Framework',\n];\n"
  },
  {
    "path": "app/Models/.gitkeep",
    "content": ""
  },
  {
    "path": "app/Views/_layouts/default.php",
    "content": "<?php\n/**\n * @var string|null $description\n * @var string|null $title\n * @var Framework\\MVC\\View $view\n */\n?>\n<!doctype html>\n<html lang=\"<?= App::language()->getCurrentLocale() ?>\" dir=\"<?= App::language()\n    ->getCurrentLocaleDirection() ?>\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"<?= isset($description)\n        ? esc($description) : 'Website built with Aplus Framework' ?>\">\n    <meta name=\"theme-color\" content=\"#000\">\n    <title><?= isset($title) ? esc($title) : 'Aplus Framework' ?></title>\n    <link rel=\"shortcut icon\" href=\"/favicon.ico\">\n    <style>\n        body {\n            background: #fff;\n            color: #000;\n            font-family: Arial, Helvetica, sans-serif;\n            font-size: 1rem;\n            line-height: 1.5rem;\n            margin: 1rem;\n        }\n\n        a {\n            color: #f0f;\n            text-decoration: none;\n        }\n\n        a:hover {\n            text-decoration: underline;\n        }\n    </style>\n</head>\n<body>\n<?= $view->renderBlock('contents') ?>\n</body>\n</html>\n"
  },
  {
    "path": "app/Views/errors/404.php",
    "content": "<?php\n/**\n * @var string $message\n * @var string $title\n * @var Framework\\MVC\\View $view\n */\n$view->extends('default');\n$view->block('contents');\n?>\n    <h1><?= $title ?></h1>\n    <p><?= $message ?></p>\n    <p>\n        <a href=\"<?= route_url('home.index') ?>\">Go to homepage</a>\n    </p>\n<?php\n$view->endBlock();\n"
  },
  {
    "path": "app/Views/home/index.php",
    "content": "<?php\n/**\n * @var Framework\\MVC\\View $view\n */\n$view->extends('default', 'contents');\n?>\n<h1><?= lang('home.title') ?></h1>\n<p><?= lang('home.description') ?></p>\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env php\n<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n(require __DIR__ . '/../boot/app.php')->runCli();\n"
  },
  {
    "path": "boot/app.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nif (is_file(__DIR__ . '/../vendor/autoload.php')) {\n    require_once __DIR__ . '/../vendor/autoload.php';\n} else {\n    require_once __DIR__ . '/init.php';\n    require_once __DIR__ . '/constants.php';\n    require_once BOOT_DIR . 'helpers.php';\n    require_once ROOT_DIR . 'App.php';\n}\n\nreturn new App(CONFIG_DIR, IS_DEV);\n"
  },
  {
    "path": "boot/constants.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * @package app\n */\n/**\n * The current environment name.\n */\ndefine('ENVIRONMENT', $_SERVER['ENVIRONMENT'] ?? 'production');\n/**\n * True if it is in development environment, otherwise false.\n */\ndefine('IS_DEV', ENVIRONMENT === 'development');\n/**\n * Path to the root directory.\n */\ndefine('ROOT_DIR', dirname(__DIR__) . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the app directory.\n */\ndefine('APP_DIR', ROOT_DIR . 'app' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the bin directory.\n */\ndefine('BIN_DIR', ROOT_DIR . 'bin' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the boot directory.\n */\ndefine('BOOT_DIR', ROOT_DIR . 'boot' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the config directory.\n */\ndefine('CONFIG_DIR', ROOT_DIR . 'config' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the public directory.\n */\ndefine('PUBLIC_DIR', ROOT_DIR . 'public' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the storage directory.\n */\ndefine('STORAGE_DIR', ROOT_DIR . 'storage' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the vendor directory.\n */\ndefine('VENDOR_DIR', ROOT_DIR . 'vendor' . \\DIRECTORY_SEPARATOR);\n/**\n * Path to the aplus directory.\n */\ndefine('APLUS_DIR', VENDOR_DIR . 'aplus' . \\DIRECTORY_SEPARATOR);\n"
  },
  {
    "path": "boot/helpers.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * @package app\n */\n\nuse Framework\\Helpers\\ArraySimple;\nuse Framework\\HTTP\\Response;\nuse Framework\\MVC\\Model;\nuse Framework\\Routing\\Route;\nuse Framework\\Session\\Session;\nuse JetBrains\\PhpStorm\\Pure;\n\n/**\n * Load helper files.\n *\n * @param array<int,string>|string $helper A list of helper names as array\n * or a helper name as string\n *\n * @return array<int,string> A list of all loaded files\n */\nfunction helpers(array | string $helper) : array\n{\n    if (is_array($helper)) {\n        $files = [];\n        foreach ($helper as $item) {\n            $files[] = helpers($item);\n        }\n        return array_merge(...$files);\n    }\n    $files = App::locator()->findFiles('Helpers/' . $helper);\n    foreach ($files as $file) {\n        require_once $file;\n    }\n    return $files;\n}\n\n/**\n * Escape special characters to HTML entities.\n *\n * @param string|null $text The text to be escaped\n * @param string $encoding The escaped text encoding\n *\n * @return string The escaped text\n */\n#[Pure]\nfunction esc(?string $text, string $encoding = 'UTF-8') : string\n{\n    $text = (string) $text;\n    return empty($text)\n        ? $text\n        : htmlspecialchars($text, \\ENT_QUOTES | \\ENT_HTML5, $encoding);\n}\n\n/**\n * Renders a view.\n *\n * @param string $path View path\n * @param array<string,mixed> $variables Variables passed to the view\n * @param string $instance The View instance name\n *\n * @return string The rendered view contents\n */\nfunction view(string $path, array $variables = [], string $instance = 'default') : string\n{\n    return App::view($instance)->render($path, $variables);\n}\n\n/**\n * Get the current URL.\n *\n * @return string\n */\nfunction current_url() : string\n{\n    return App::request()->getUrl()->toString();\n}\n\n/**\n * Get the current Route.\n *\n * @return Route\n */\nfunction current_route() : Route\n{\n    return App::router()->getMatchedRoute();\n}\n\n/**\n * Get a URL based in a Route name.\n *\n * @param string $name Route name\n * @param array<mixed> $pathArgs Route path arguments\n * @param array<mixed> $originArgs Route origin arguments\n *\n * @return string The Route URL\n */\nfunction route_url(string $name, array $pathArgs = [], array $originArgs = []) : string\n{\n    $route = App::router()->getNamedRoute($name);\n    $matched = App::router()->getMatchedRoute();\n    if (empty($originArgs)\n        && $matched\n        && $route->getOrigin() === $matched->getOrigin()\n    ) {\n        $originArgs = App::router()->getMatchedOriginArguments();\n    }\n    return $route->getUrl($originArgs, $pathArgs);\n}\n\n/**\n * Renders a language file line with dot notation format.\n *\n * e.g. home.hello matches 'home' for file and 'hello' for line.\n *\n * @param string $line The dot notation file line\n * @param array<int|string,string> $args The arguments to be used in the\n * formatted text\n * @param string|null $locale A custom locale or null to use the current\n *\n * @return string|null The rendered text or null if not found\n */\nfunction lang(string $line, array $args = [], ?string $locale = null) : ?string\n{\n    return App::language()->lang($line, $args, $locale);\n}\n\n/**\n * Get the Session instance.\n *\n * @return Session\n */\nfunction session() : Session\n{\n    return App::session();\n}\n\n/**\n * Get data from old redirect.\n *\n * @param string|null $key Set null to return all data\n * @param bool $escape\n *\n * @see Framework\\HTTP\\Request::getRedirectData()\n * @see Framework\\HTTP\\Response::redirect()\n * @see redirect()\n *\n * @return mixed The old value. If $escape is true and the value is not\n * stringable, an empty string will return\n */\nfunction old(?string $key, bool $escape = true) : mixed\n{\n    App::session()->activate();\n    $data = App::request()->getRedirectData($key);\n    if ($data !== null && $escape) {\n        $data = is_scalar($data) || (is_object($data) && method_exists($data, '__toString'))\n            ? esc((string) $data)\n            : '';\n    }\n    return $data;\n}\n\n/**\n * Tells if session has old data.\n *\n * @param string|null $key null to check all data or a specific key in the\n * array simple format\n *\n * @see old()\n *\n * @return bool\n */\nfunction has_old(?string $key = null) : bool\n{\n    App::session()->activate();\n    return App::request()->getRedirectData($key) !== null;\n}\n\n/**\n * Renders the AntiCSRF input.\n *\n * @param string $instance The antiCsrf service instance name\n *\n * @return string An HTML hidden input if antiCsrf service is enabled or an\n * empty string if it is disabled\n */\nfunction csrf_input(string $instance = 'default') : string\n{\n    return App::antiCsrf($instance)->input();\n}\n\n/**\n * Set Response status as \"404 Not Found\" and auto set body as\n * JSON or HTML page based on Request Content-Type header.\n *\n * @param array<string,mixed> $variables\n *\n * @return Response\n */\nfunction respond_not_found(array $variables = []) : Response\n{\n    $request = App::request();\n    $response = App::response();\n    $response->setStatus(404);\n    if ($request->isJson() || $request->negotiateAccept([\n            'text/html',\n            'application/json',\n        ]) === 'application/json') {\n        return $response->setJson([\n            'error' => [\n                'code' => 404,\n                'reason' => 'Not Found',\n            ],\n        ]);\n    }\n    $variables['title'] ??= lang('routing.error404');\n    $variables['message'] ??= lang('routing.pageNotFound');\n    return $response->setBody(\n        view('errors/404', $variables)\n    );\n}\n\n/**\n * Sets the HTTP Redirect Response with data accessible in the next HTTP\n * Request.\n *\n * @param string $location Location Header value\n * @param array<int|string,mixed> $data Session data available on next\n * Request\n * @param int|null $code HTTP Redirect status code. Leave null to determine\n * based on the current HTTP method.\n *\n * @see http://en.wikipedia.org/wiki/Post/Redirect/Get\n * @see Framework\\HTTP\\Request::getRedirectData()\n * @see old()\n *\n * @throws InvalidArgumentException for invalid Redirection code\n *\n * @return Response\n */\nfunction redirect(string $location, array $data = [], ?int $code = null) : Response\n{\n    if ($data) {\n        App::session()->activate();\n    }\n    return App::response()->redirect($location, $data, $code);\n}\n\n/**\n * Redirect to a named route.\n *\n * @param array<mixed>|string $route route name as string or an array with the\n * route name, an array with path args and other array with origin args\n * @param array<mixed> $data Session data available on next\n * Request\n * @param int|null $code HTTP Redirect status code. Leave null to determine\n * based on the current HTTP method.\n *\n * @see http://en.wikipedia.org/wiki/Post/Redirect/Get\n * @see Framework\\HTTP\\Request::getRedirectData()\n * @see old()\n * @see redirect()\n *\n * @throws InvalidArgumentException for invalid Redirection code\n *\n * @return Response\n */\nfunction redirect_to(\n    array | string $route,\n    array $data = [],\n    ?int $code = null\n) : Response {\n    $route = (array) $route;\n    $route = route_url(...$route);\n    return redirect($route, $data, $code);\n}\n\n/**\n * Get configs from a service.\n *\n * @param string $name The service name\n * @param string $key The instance name and, optionally, with keys in the\n * ArraySimple keys format\n *\n * @return mixed The key value\n */\nfunction config(string $name, string $key = 'default') : mixed\n{\n    [$instance, $keys] = array_pad(explode('[', $key, 2), 2, null);\n    $config = App::config()->get($name, $instance);\n    if ($keys === null) {\n        return $config;\n    }\n    $pos = strpos($keys, ']');\n    if ($pos === false) {\n        $pos = strlen($key);\n    }\n    $parent = substr($keys, 0, $pos);\n    $keys = substr($keys, $pos + 1);\n    $key = $parent . $keys;\n    return ArraySimple::value($key, $config);\n}\n\n/**\n * Get same Model instance.\n *\n * @template T of Model\n *\n * @param class-string<T> $class\n *\n * @return T\n */\nfunction model(string $class) : Model\n{\n    return Model::get($class);\n}\n\n/**\n * Get an environment variable.\n *\n * @param string $key\n * @param mixed $default\n *\n * @return mixed\n */\nfunction env(string $key, mixed $default = null) : mixed\n{\n    return $_ENV[$key] ?? $default;\n}\n"
  },
  {
    "path": "boot/init.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nif (is_file(__DIR__ . '/../.env.php')) {\n    require __DIR__ . '/../.env.php';\n    if (isset($_ENV['ENVIRONMENT'])) {\n        $_SERVER['ENVIRONMENT'] = $_ENV['ENVIRONMENT'];\n    }\n}\n\nif (isset($_SERVER['ENVIRONMENT']) && $_SERVER['ENVIRONMENT'] === 'development') {\n    error_reporting(-1);\n    ini_set('display_errors', 'On');\n} else {\n    error_reporting(\\E_ALL & ~\\E_DEPRECATED & ~\\E_NOTICE & ~\\E_USER_DEPRECATED & ~\\E_USER_NOTICE);\n    ini_set('display_errors', 'Off');\n}\n\ndate_default_timezone_set('UTC');\n//set_time_limit(30);\n"
  },
  {
    "path": "boot/routes.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nuse Framework\\Routing\\RouteCollection;\n\nApp::router()->serve(\n    env('app.default.origin', 'http://localhost:8080'),\n    static function (RouteCollection $routes) : void {\n        $routes->namespace('App\\Controllers', [\n            $routes->get('/', 'Home::index', 'home.index'),\n        ]);\n        $routes->notFound(static fn () => respond_not_found());\n    }\n);\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"aplus/app\",\n    \"description\": \"Aplus Framework App Project\",\n    \"license\": \"MIT\",\n    \"type\": \"project\",\n    \"keywords\": [\n        \"app\",\n        \"application\",\n        \"libraries\",\n        \"mvc\",\n        \"database\",\n        \"routing\",\n        \"http\",\n        \"config\",\n        \"session\",\n        \"skeleton\",\n        \"project\"\n    ],\n    \"homepage\": \"https://aplus-framework.com/packages/app\",\n    \"support\": {\n        \"email\": \"support@aplus-framework.com\",\n        \"issues\": \"https://github.com/aplus-framework/app/issues\",\n        \"forum\": \"https://aplus-framework.com/forum\",\n        \"source\": \"https://github.com/aplus-framework/app\",\n        \"docs\": \"https://docs.aplus-framework.com/guides/projects/app/\"\n    },\n    \"funding\": [\n        {\n            \"type\": \"Aplus Sponsor\",\n            \"url\": \"https://aplus-framework.com/sponsor\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.3\",\n        \"aplus/framework\": \"^25.1\"\n    },\n    \"require-dev\": {\n        \"ext-xdebug\": \"*\",\n        \"aplus/coding-standard\": \"^2.8\",\n        \"aplus/testing\": \"^3.0\",\n        \"ergebnis/composer-normalize\": \"^2.44\",\n        \"jetbrains/phpstorm-attributes\": \"^1.1\",\n        \"natanfelles/php-server\": \"^2.11\",\n        \"phpmd/phpmd\": \"^2.15\",\n        \"phpstan/phpstan\": \"^1.12\"\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true,\n    \"autoload\": {\n        \"psr-4\": {\n            \"App\\\\\": \"app/\"\n        },\n        \"classmap\": [\n            \"App.php\"\n        ],\n        \"files\": [\n            \"boot/init.php\",\n            \"boot/constants.php\",\n            \"boot/helpers.php\"\n        ]\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tests\\\\\": \"tests/\"\n        }\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"ergebnis/composer-normalize\": true\n        },\n        \"optimize-autoloader\": true,\n        \"preferred-install\": \"dist\",\n        \"sort-packages\": true\n    }\n}\n"
  },
  {
    "path": "config/antiCsrf.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Anti-CSRF config.\n *\n * @see App::antiCsrf()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#anti-csrf-service\n */\nreturn [\n    'default' => [\n        'enabled' => true,\n        'token_name' => 'csrf_token',\n        'token_bytes_length' => 8,\n        'generate_token_function' => 'base64_encode',\n        'request_instance' => 'default',\n        'session_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/autoloader.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Autoloader config.\n *\n * @see App::autoloader()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#autoloader-service\n */\nreturn [\n    'default' => [\n        'register' => true,\n        'extensions' => '.php',\n        'namespaces' => [\n            'App' => APP_DIR,\n        ],\n        'classes' => [],\n    ],\n];\n"
  },
  {
    "path": "config/cache.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Cache config.\n *\n * @see App::cache()\n * @see Framework\\Cache\\FilesCache::$configs\n * @see Framework\\Cache\\MemcachedCache::$configs\n * @see Framework\\Cache\\RedisCache::$configs\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#cache-service\n */\n\nuse Framework\\Cache\\FilesCache;\nuse Framework\\Cache\\Serializer;\n\nreturn [\n    'default' => [\n        'class' => env('cache.default.class', FilesCache::class),\n        'configs' => env('cache.default.configs', [\n            'directory' => STORAGE_DIR . 'cache',\n        ]),\n        'prefix' => env('cache.default.prefix'),\n        'serializer' => Serializer::PHP,\n        'default_ttl' => null,\n        'logger_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/console.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Console config.\n *\n * @see App::console()\n * @see App::run()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#console-service\n */\nreturn [\n    'default' => [\n        'find_in_namespaces' => false,\n        'directories' => [\n            APLUS_DIR . 'dev-commands/src',\n            APP_DIR . 'Commands',\n        ],\n        'commands' => null,\n        'language_instance' => 'default',\n        'locator_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/database.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Database config.\n *\n * @see App::database()\n * @see Framework\\Database\\Database::makeConfig()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#database-service\n */\nreturn [\n    'default' => [\n        'config' => [\n            'username' => env('database.default.config.username', 'root'),\n            'password' => env('database.default.config.password', 'password'),\n            'schema' => env('database.default.config.schema', 'framework-tests'),\n            'host' => env('database.default.config.host', 'localhost'),\n            'port' => env('database.default.config.port', 3306),\n        ],\n        'logger_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/debugger.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Debugger config.\n *\n * @see App::debugger()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#debugger-service\n */\nreturn [\n    'default' => [\n        'debugbar_view' => null,\n        'options' => [\n            'color' => 'magenta',\n            'icon_path' => null,\n            'info_contents' => null,\n        ],\n    ],\n];\n"
  },
  {
    "path": "config/exceptionHandler.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Exceptions config.\n *\n * @see App::exceptionHandler()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#exception-handler-service\n */\n\nuse Framework\\Debug\\ExceptionHandler;\n\nreturn [\n    'default' => [\n        'initialize' => true,\n        'handle_errors' => true,\n        'environment' => IS_DEV\n            ? ExceptionHandler::DEVELOPMENT\n            : ExceptionHandler::PRODUCTION,\n        'development_view' => null,\n        'production_view' => null,\n        'search_engine' => null,\n        'show_log_id' => null,\n        'json_flags' => null,\n        'hidden_inputs' => null,\n        'logger_instance' => 'default',\n        'language_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/language.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Language config.\n *\n * @see App::language()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#language-service\n */\n\nuse Framework\\Language\\FallbackLevel;\n\nreturn [\n    'default' => [\n        'default' => 'en',\n        'current' => 'en',\n        'supported' => [\n            'en',\n            'es',\n            'pt-br',\n        ],\n        'fallback_level' => FallbackLevel::none,\n        'directories' => [\n            APP_DIR . 'Languages',\n        ],\n        'find_in_namespaces' => false,\n        'negotiate' => false,\n        'autoloader_instance' => 'default',\n        'request_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/locator.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Locator config.\n *\n * @see App::locator()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#locator-service\n */\nreturn [\n    'default' => [\n        'autoloader_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/logger.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Logger config.\n *\n * @see App::logger()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#logger-service\n */\n\nuse Framework\\Log\\Loggers\\MultiFileLogger;\nuse Framework\\Log\\LogLevel;\n\nreturn [\n    'default' => [\n        'class' => env('logger.default.class', MultiFileLogger::class),\n        'destination' => env('logger.default.destination', STORAGE_DIR . 'logs'),\n        'level' => env('logger.default.level', LogLevel::DEBUG),\n        'config' => [],\n    ],\n];\n"
  },
  {
    "path": "config/mailer.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Mailer config.\n *\n * @see App::mailer()\n * @see Framework\\Email\\Mailer::makeConfig()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#mailer-service\n */\n\nreturn [\n    'default' => [\n        'host' => env('mailer.default.host', 'localhost'),\n        'port' => env('mailer.default.port', 587),\n        'tls' => env('mailer.default.tls', true),\n        'username' => env('mailer.default.username'),\n        'password' => env('mailer.default.password'),\n        'charset' => env('mailer.default.charset', 'utf-8'),\n        'crlf' => env('mailer.default.crlf', \"\\r\\n\"),\n        'keep_alive' => env('mailer.default.keep_alive', false),\n        'save_logs' => env('mailer.default.save_logs', false),\n    ],\n];\n"
  },
  {
    "path": "config/migrator.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Migrator config.\n *\n * @see App::migrator()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#migrator-service\n */\n\nreturn [\n    'default' => [\n        'directories' => [\n            APP_DIR . 'Migrations',\n        ],\n        'table' => 'Migrations',\n        'database_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/request.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Request config.\n *\n * @see App::request()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#request-service\n */\nreturn [\n    'default' => [\n        'allowed_hosts' => env('request.default.allowed_hosts', []),\n        'force_https' => env('request.default.force_https', false),\n        'server_vars' => [],\n        'json_flags' => null,\n    ],\n];\n"
  },
  {
    "path": "config/response.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Response config.\n *\n * @see App::response()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#response-service\n */\nreturn [\n    'default' => [\n        'headers' => [],\n        'auto_etag' => false,\n        'auto_language' => false,\n        'cache' => null,\n        'csp' => [],\n        'csp_report_only' => [],\n        'json_flags' => null,\n        'replace_headers' => null,\n        'language_instance' => 'default',\n        'request_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/router.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Router config.\n *\n * @see App::router()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#router-service\n */\nreturn [\n    'default' => [\n        'files' => [\n            BOOT_DIR . 'routes.php',\n        ],\n        'auto_options' => false,\n        'auto_methods' => false,\n        'placeholders' => [],\n        'response_instance' => 'default',\n        'language_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/session.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Session config.\n *\n * @see App::session()\n * @see Framework\\Session\\Session::setOptions()\n * @see Framework\\Session\\SaveHandlers\\DatabaseHandler::prepareConfig()\n * @see Framework\\Session\\SaveHandlers\\FilesHandler::prepareConfig()\n * @see Framework\\Session\\SaveHandlers\\MemcachedHandler::prepareConfig()\n * @see Framework\\Session\\SaveHandlers\\RedisHandler::prepareConfig()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#session-service\n */\n\nuse Framework\\Session\\SaveHandlers\\FilesHandler;\n\nreturn [\n    'default' => [\n        'options' => [],\n        'save_handler' => [\n            'class' => FilesHandler::class,\n            'config' => [\n                'prefix' => '',\n                'directory' => STORAGE_DIR . 'sessions',\n                'match_ip' => false,\n                'match_ua' => false,\n            ],\n        ],\n        'logger_instance' => 'default',\n        'auto_start' => true,\n    ],\n];\n"
  },
  {
    "path": "config/validation.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * Validation config.\n *\n * @see App::validation()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#validation-service\n */\n\nuse Framework\\MVC\\Validator;\nuse Framework\\Validation\\FilesValidator;\n\nreturn [\n    'default' => [\n        'validators' => [\n            Validator::class,\n            FilesValidator::class,\n        ],\n        'language_instance' => 'default',\n    ],\n];\n"
  },
  {
    "path": "config/view.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n/**\n * View config.\n *\n * @see App::view()\n * @see https://docs.aplus-framework.com/guides/libraries/mvc/index.html#view-service\n */\nreturn [\n    'default' => [\n        'base_dir' => APP_DIR . 'Views',\n        'extension' => '.php',\n        'layout_prefix' => '_layouts',\n        'include_prefix' => '_includes',\n        'show_debug_comments' => true,\n        'throw_exceptions_in_destructor' => null,\n    ],\n];\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3.0\"\nservices:\n  lempa:\n    image: registry.gitlab.com/aplus-framework/images/lempa:4\n    container_name: lempa-app\n    working_dir: /var/www/aplus\n    volumes:\n      - .:/var/www/aplus\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    #environment:\n    #  - PRELOAD=/var/www/aplus/preload.php\n    tty: true\n  lempa-latest:\n    image: registry.gitlab.com/aplus-framework/images/lempa:latest\n    container_name: lempa-app-latest\n    working_dir: /var/www/aplus\n    volumes:\n      - .:/var/www/aplus\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    #environment:\n    #  - PRELOAD=/var/www/aplus/preload.php\n    tty: true\n"
  },
  {
    "path": "guide/index.rst",
    "content": "App\n===\n\n.. image:: image.png\n    :alt: Aplus Framework App Project\n\nAplus Framework App Project.\n\n- `Installation`_\n- `Structure`_\n- `Bootstrap`_\n- `Configuration`_\n- `Storage`_\n- `The global class App`_\n- `The App namespace`_\n- `Running an Aplus App`_\n- `Testing`_\n- `Deployment`_\n- `Conclusion`_\n\nInstallation\n------------\n\nThe installation of this project can be done with Composer:\n\n.. code-block::\n\n    composer create-project aplus/app\n\nOr, to install the latest `LTS <https://aplus-framework.com/lts>`_ version:\n\n.. code-block::\n\n    composer create-project aplus/app:^24\n\nStructure\n---------\n\nThe App has a standard structure to organize business logic.\n\nAnd remember, it's highly customizable. You can adapt it as you like.\n\nThis is the basic directory tree:\n\n.. code-block::\n\n    .\n    ├── app/\n    │   ├── Commands/\n    │   │   └── Index.php\n    │   ├── Controllers/\n    │   │   └── Home.php\n    │   ├── Languages/\n    │   ├── Models/\n    │   └── Views/\n    ├── App.php\n    ├── bin/\n    │   └── console\n    ├── boot/\n    │   ├── app.php\n    │   ├── constants.php\n    │   ├── helpers.php\n    │   ├── init.php\n    │   └── routes.php\n    ├── composer.json\n    ├── config/\n    ├── .env.php\n    ├── preload.php\n    ├── public/\n    │   └── index.php\n    ├── storage/\n    ├── tests/\n    └── vendor/\n\nBootstrap\n---------\n\nInside the **boot** directory are located files that are part of the application\nstartup.\n\nApp\n###\n\nThe **app.php** file is responsible for loading the files needed for the app to work.\nSuch as the Composer autoloader or the initialization files.\n\nIt returns an instance of the ``App`` class, which is called to run the application\nin HTTP or CLI.\n\nInit\n####\n\nThe **init.php** file is responsible for setting the environment variables\nthat are defined in the **.env.php** file.\n\nAlso, initial settings are performed, such as setting\n`error_reporting <https://www.php.net/manual/en/function.error-reporting.php>`_\nand `display_errors <https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors>`_.\n\nConstants\n#########\n\nIn the **constants.php** file, the constants that will be available throughout\nthe application are set, such as the **ENVIRONMENT** and the paths to the\ndifferent directories.\n\nHelpers\n#######\n\nThe **helpers.php** file contains common functions that will always be available\nin the application.\n\nRoutes\n######\n\nIn the **routes.php** file, the application routes are served.\n\n\nConfiguration\n-------------\n\nAplus App is organized in such a way that its configuration files are all in the\nsame directory.\n\nBy default, the directory is called **config**. Located in the application's root\ndirectory.\n\nConfiguration files serve to pre-establish values used by services\nor routines needed for `helpers`_ and `libraries <https://docs.aplus-framework.com/guides/libraries/index.html>`_.\n\nFor more details see the `Config <https://docs.aplus-framework.com/guides/libraries/config/index.html>`_\nand `MVC <https://docs.aplus-framework.com/guides/libraries/mvc/index.html>`_\nlibraries documentation.\n\nStorage\n-------\n\nIn the **storage** directory, different types of files are stored in\nsubdirectories:\n\nCache\n#####\n\nCache files are stored in the **cache** directory.\n\nLogs\n####\n\nLog files are stored in the **logs** directory.\n\nSessions\n########\n\nSession files are stored in the **sessions** directory.\n\nUploads\n#######\n\nUpload files are stored in the **uploads** directory.\n\nThe global class App\n--------------------\n\nThe global class ``App``, whose file is located in the root directory, extends\nthe ``Framework\\MVC\\App`` class.\n\nThrough it, it is possible to customize features and\n`services <https://docs.aplus-framework.com/guides/libraries/mvc/index.html#services>`_.\n\nThe App namespace\n-----------------\n\nInside the **app** directory is registered the ``App`` namespace.\n\nBy default, some files are already inside it:\n\nCommands\n########\n\nIn the **Commands** directory is the ``App\\Commands`` namespace.\n\nIn it, you can add commands that will be available in the console.\n\nControllers\n###########\n\nIn the **Controllers** directory is the ``App\\Controllers`` namespace.\n\nIn it, you can add controllers with methods that will act as routes.\n\nLanguages\n#########\n\nIn the subdirectories of **Languages** are stored application language files.\n\nModels\n######\n\nIn the **Models** directory is the ``App\\Models`` namespace.\n\nIn it it is possible to add models that represent tables of the application's\ndatabase schema.\n\nViews\n#####\n\nIn the **Views** directory are stored application view files.\n\nRunning an Aplus App\n--------------------\n\nThe Aplus App project is designed to run on HTTP and CLI.\n\nRun HTTP\n########\n\nInside the **public** directory is the front-controller **index.php**.\n\nThe **public** directory must be the document root configured on the server.\n\nNote that the directory name may vary by server. In some it may be called\n**public_html** and in others **web**, etc.\n\nIn development, you can use PHP server running ``vendor/bin/php-server`` or\nDocker Compose.\n\nRun CLI\n#######\n\nInside the **bin** directory is the **console** file.\n\nThrough it it is possible to run the various commands of the application,\nrunning ``./bin/console``.\n\nTesting\n-------\n\nUnit tests can be created within the **tests** directory. See the tests that\ncome inside it as an example.\n\nDeployment\n----------\n\nWe will see how to deploy to a `Shared Hosting`_ and a `Private Server`_:\n\nIn the following examples, configurations will be made for the domain **domain.tld**.\nReplace it with the domain of your application.\n\nShared Hosting\n##############\n\nIn shared hosting, it is common that you can upload the project files only by FTP.\n\nAlso, typically the document root is a publicly accessible directory called\n**www**, **web** or **public_html**.\n\nAnd the server is Apache, which allows configurations through files called\n**.htaccess**.\n\nIn the following example the settings can be made locally and then sent to the\nhosting server.\n\nEnvironment Variables\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEnvironment variables are defined in the **.env.php** file.\n\nEdit them according to the examples below:\n\nENVIRONMENT\n^^^^^^^^^^^\n\nMake sure ENVIRONMENT is set to ``production``:\n\n.. code-block:: php\n\n    $_ENV['ENVIRONMENT'] = 'production';\n\nURL Origin\n^^^^^^^^^^\n\nMake sure that the URL Origin has the correct domain:\n\n.. code-block:: php\n\n    $_ENV['app.default.origin'] = 'http://domain.tld';\n\nInstall Dependencies\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nInstall dependencies with Composer:\n\n.. code-block::\n\n    composer install --no-dev\n\n.htaccess files\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIn the document root and in the **public** directory of the application has\n**.htaccess** files that can be configured as needed.\n\nFor example, redirecting insecure requests to **HTTPS** or redirecting to the\n**www** subdomain.\n\nFinishing\n\"\"\"\"\"\"\"\"\"\n\nUpload the files to the public directory of your hosting.\n\nAccess the domain through the browser: http://domain.tld\n\nIt should open the home page of your project.\n\nPrivate Server\n##############\n\nWe will be using Ubuntu 24.04 LTS which is supported until 2029 and already\ncomes with PHP 8.3.\n\nReplace ``domain.tld`` with your domain.\n\nInstalling PHP and required packages:\n\n.. code-block::\n\n    sudo apt-get -y install \\\n    composer \\\n    curl \\\n    git \\\n    php8.3-apcu \\\n    php8.3-cli \\\n    php8.3-curl \\\n    php8.3-fpm \\\n    php8.3-gd \\\n    php8.3-igbinary \\\n    php8.3-intl \\\n    php8.3-mbstring \\\n    php8.3-memcached \\\n    php8.3-msgpack \\\n    php8.3-mysql \\\n    php8.3-opcache \\\n    php8.3-readline \\\n    php8.3-redis \\\n    php8.3-xml \\\n    php8.3-yaml \\\n    php8.3-zip \\\n    unzip\n\nMake the application directory:\n\n.. code-block::\n\n    sudo mkdir -p /var/www/domain.tld\n\nSet directory ownership. Replace \"username\" with your username:\n\n.. code-block::\n\n    sudo chown username:username /var/www/domain.tld\n\nEnter the application directory...\n\n.. code-block::\n\n    cd /var/www/domain.tld\n\n... and clone or download your project.\n\nAs an example, we'll install a new app:\n\n.. code-block::\n\n    git clone https://github.com/aplus-framework/app.git .\n\nSet the owner of the storage directory:\n\n.. code-block::\n\n    sudo chown -R www-data:www-data storage\n\nEdit the Environment and the URL Origin of your project in the **.env.php**\nfile:\n\n.. code-block:: php\n\n    $_ENV['ENVIRONMENT'] = 'production';\n    $_ENV['app.default.origin'] = 'http://domain.tld';\n\nInstall the necessary PHP packages through Composer:\n\n.. code-block::\n\n    composer install --no-dev --ignore-platform-req=ext-xdebug\n\n* We use ``install`` instead of ``update`` to respect the **composer.lock** file if it exists in your repository.\n\n* We use ``--ignore-platform-req=ext-xdebug`` because we don't need the xdebug extension in production.\n\nWeb Servers\n\"\"\"\"\"\"\"\"\"\"\"\n\nIn these examples, we will see how to install and configure two web servers:\n\n- `Apache`_\n- `Nginx (recommended)`_\n\nApache\n^^^^^^\n\nInstall required packages:\n\n.. code-block::\n\n    sudo apt install apache2 libapache2-mod-php\n\nEnable modules:\n\n.. code-block::\n\n    sudo a2enmod rewrite\n\nCreate the file **/etc/apache2/sites-available/domain.tld.conf**:\n\n.. code-block:: apacheconf\n\n    <Directory /var/www/domain.tld/public>\n        Options Indexes FollowSymLinks\n        AllowOverride All\n        Require all granted\n    </Directory>\n    <VirtualHost *:80>\n        ServerName domain.tld\n        SetEnv ENVIRONMENT production\n        DocumentRoot /var/www/domain.tld/public\n    </VirtualHost>\n\nEnable the site:\n\n.. code-block::\n\n    sudo a2ensite domain.tld\n\nReload the server:\n\n.. code-block::\n\n    sudo systemctl reload apache2\n\nAccess the domain through the browser: http://domain.tld\n\nIt should open the home page of your project.\n\nNginx (recommended)\n^^^^^^^^^^^^^^^^^^^\n\nEdit the **php.ini** file:\n\n.. code-block::\n\n    sudo sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/8.3/fpm/php.ini\n\nRestart PHP-FPM:\n\n.. code-block::\n\n    sudo systemctl restart php8.3-fpm\n\nInstall required packages:\n\n.. code-block::\n\n    sudo apt install nginx\n\nCreate the file **/etc/nginx/sites-available/domain.tld.conf**:\n\n.. code-block:: nginx\n\n    server {\n        listen 80;\n\n        root /var/www/domain.tld/public;\n\n        index index.php;\n\n        server_name domain.tld;\n\n        location / {\n            try_files $uri $uri/ /index.php?$args;\n        }\n\n        location ~ \\.php$ {\n            include snippets/fastcgi-php.conf;\n            fastcgi_param ENVIRONMENT production;\n            fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;\n        }\n\n        location ~ /\\. {\n            deny all;\n        }\n    }\n\nEnable the site:\n\n.. code-block::\n\n    sudo ln -s /etc/nginx/sites-available/domain.tld.conf /etc/nginx/sites-enabled/\n\nTest Nginx configurations:\n\n.. code-block::\n\n    sudo nginx -t\n\nRestart Nginx:\n\n.. code-block::\n\n    sudo systemctl restart nginx\n\nAccess the domain through the browser: http://domain.tld\n\nIt should open the home page of your project.\n\nConclusion\n----------\n\nAplus App Project is an easy-to-use tool for, beginners and experienced, PHP developers. \nIt is perfect for building powerful, high-performance applications. \nThe more you use it, the more you will learn.\n\n.. note::\n    Did you find something wrong? \n    Be sure to let us know about it with an\n    `issue <https://github.com/aplus-framework/app/issues>`_. \n    Thank you!\n"
  },
  {
    "path": "php-server.ini",
    "content": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n; php-server configuration file - https://github.com/natanfelles/php-server ;\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\nphp = PHP_BINARY\nhost = localhost\nport = 8080\nroot = ./public\nautoindex = true\nindex = index.html index.php\nerror_reporting = E_ALL\n\n[ini]\ndisplay_errors = 1\ndisplay_startup_errors = 1\nmax_execution_time = 30\npost_max_size = 8M\nupload_max_filesize = 2M\n;opcache.preload = preload.php\n\nxdebug.mode=debug,develop\nxdebug.var_display_max_depth = 10\nxdebug.var_display_max_children = 256\nxdebug.var_display_max_data = 1024\n\n[server]\n;ENVIRONMENT = development\n"
  },
  {
    "path": "phpdoc.dist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<phpdocumentor\n    configVersion=\"3\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns=\"https://www.phpdoc.org\"\n    xsi:noNamespaceSchemaLocation=\"https://docs.phpdoc.org/latest/phpdoc.xsd\"\n>\n    <title>Aplus Framework App Project</title>\n    <paths>\n        <output>build/docs</output>\n        <cache>build/docs-cache</cache>\n    </paths>\n    <version number=\"latest\">\n        <folder>latest</folder>\n        <api>\n            <source dsn=\".\">\n                <path>app</path>\n                <path>extra</path>\n                <path>App.php</path>\n            </source>\n            <default-package-name>application</default-package-name>\n            <include-source>true</include-source>\n        </api>\n        <guide>\n            <source dsn=\".\">\n                <path>guide</path>\n            </source>\n            <output>guide</output>\n        </guide>\n    </version>\n    <setting name=\"graphs.enabled\" value=\"true\"/>\n    <setting name=\"guides.enabled\" value=\"true\"/>\n    <template name=\"default\"/>\n</phpdocumentor>\n\n"
  },
  {
    "path": "phpmd.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset name=\"Mess Detector\">\n    <!-- cleancode -->\n    <rule ref=\"rulesets/cleancode.xml\">\n        <exclude name=\"BooleanArgumentFlag\"/>\n        <exclude name=\"StaticAccess\"/>\n    </rule>\n    <!-- codesize -->\n    <rule ref=\"rulesets/codesize.xml\"/>\n    <!-- controversial -->\n    <rule ref=\"rulesets/controversial.xml\"/>\n    <!-- design -->\n    <rule ref=\"rulesets/design.xml\"/>\n    <!-- naming -->\n    <rule ref=\"rulesets/naming.xml\"/>\n    <!-- unusedcode -->\n    <rule ref=\"rulesets/unusedcode.xml\"/>\n</ruleset>\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "parameters:\n    level: 7\n    paths:\n        - app\n        - boot\n        - config\n        - public\n        - tests\n    dynamicConstantNames:\n        - ENVIRONMENT\n        - IS_DEV\n        - ROOT_DIR\n        - APP_DIR\n        - BIN_DIR\n        - BOOT_DIR\n        - CONFIG_DIR\n        - PUBLIC_DIR\n        - STORAGE_DIR\n        - VENDOR_DIR\n        - APLUS_DIR\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" backupGlobals=\"false\"\n         bootstrap=\"vendor/autoload.php\" colors=\"true\" stopOnError=\"false\" stopOnFailure=\"false\"\n         stopOnIncomplete=\"false\" stopOnSkipped=\"false\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/11.2/phpunit.xsd\"\n         cacheDirectory=\".phpunit.cache\">\n    <coverage>\n        <report>\n            <clover outputFile=\"build/coverage/clover.xml\"/>\n            <html outputDirectory=\"build/coverage\"/>\n            <text outputFile=\"php://stdout\"/>\n        </report>\n    </coverage>\n    <testsuite name=\"Tests\">\n        <directory suffix=\"Test.php\">tests</directory>\n    </testsuite>\n    <logging/>\n    <source>\n        <include>\n            <directory suffix=\".php\">app</directory>\n            <directory suffix=\".php\">config</directory>\n            <file>.env.php</file>\n            <file>App.php</file>\n            <file>bin/console</file>\n            <file>boot/helpers.php</file>\n            <file>boot/routes.php</file>\n            <file>public/index.php</file>\n        </include>\n    </source>\n</phpunit>\n"
  },
  {
    "path": "preload.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nrequire __DIR__ . '/vendor/aplus/autoload/src/Preloader.php';\n\nuse Framework\\Autoload\\Preloader;\n\n$files = (new Preloader())->load();\necho 'Preloading: ' . \\PHP_EOL;\nforeach ($files as $index => $file) {\n    echo ++$index . ') ' . $file . \\PHP_EOL;\n}\necho 'Total of ' . count($files) . ' preloaded files.' . \\PHP_EOL . \\PHP_EOL;\n"
  },
  {
    "path": "public/.htaccess",
    "content": "Options All -Indexes\n\n<IfModule mod_rewrite.c>\n    RewriteEngine On\n\n    # Redirect non-www to HTTPS www\n    #RewriteCond %{HTTP_HOST} !^www\\. [NC]\n    #RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [L,R=301]\n\n    # Redirect to HTTPS\n    #RewriteCond %{HTTPS} off\n    #RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]\n\n    # Send requests to front controller\n    RewriteCond %{REQUEST_FILENAME} !-f\n    RewriteCond %{REQUEST_FILENAME} !-d\n    RewriteRule ^ index.php [QSA,L]\n</IfModule>\n"
  },
  {
    "path": "public/index.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n(require __DIR__ . '/../boot/app.php')->runHttp();\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nDisallow:\n"
  },
  {
    "path": "storage/cache/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "storage/logs/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "storage/sessions/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "storage/uploads/.gitignore",
    "content": "*\n!.gitignore\n"
  },
  {
    "path": "tests/AppTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests;\n\nuse App;\nuse Framework\\Config\\Config;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\n\n#[RunTestsInSeparateProcesses]\nfinal class AppTest extends TestCase\n{\n    protected function setUp() : void\n    {\n        //\n    }\n\n    public function testInstance() : void\n    {\n        self::assertInstanceOf(\n            \\Framework\\MVC\\App::class,\n            new App(new Config(CONFIG_DIR))\n        );\n    }\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests;\n\nabstract class TestCase extends \\Framework\\Testing\\TestCase\n{\n    protected array | string | null $configs = CONFIG_DIR;\n}\n"
  },
  {
    "path": "tests/app/Commands/IndexTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\app\\Commands;\n\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Tests\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class IndexTest extends TestCase\n{\n    public function testRun() : void\n    {\n        $this->app->runCli('index');\n        self::assertStdoutContains('index');\n    }\n}\n"
  },
  {
    "path": "tests/app/Controllers/HomeTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\app\\Controllers;\n\nuse Framework\\HTTP\\Status;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Tests\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class HomeTest extends TestCase\n{\n    public function testIndex() : void\n    {\n        $this->app->runHttp('http://localhost:8080');\n        self::assertResponseStatusCode(Status::OK);\n        self::assertResponseBodyContains('Aplus Framework');\n        self::assertMatchedRouteName('home.index');\n    }\n}\n"
  },
  {
    "path": "tests/app/Languages/LanguagesTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\app\\Languages;\n\nuse App;\nuse Tests\\TestCase;\n\nfinal class LanguagesTest extends TestCase\n{\n    protected string $langDir = APP_DIR . 'Languages/';\n\n    public function testKeys() : void\n    {\n        $defaultLocale = App::language()->getDefaultLocale();\n        foreach ($this->getFiles($defaultLocale) as $file) {\n            $rules = require $this->langDir . $defaultLocale . '/' . $file;\n            $rules = \\array_keys($rules);\n            foreach ($this->getCodes() as $code) {\n                $lines = require $this->langDir . $code . '/' . $file;\n                $lines = \\array_keys($lines);\n                \\sort($lines);\n                self::assertSame(\n                    $rules,\n                    $lines,\n                    \\sprintf(\n                        'Comparing \"%s\" with \"%s\"',\n                        $defaultLocale . '/' . $file,\n                        $code . '/' . $file\n                    )\n                );\n            }\n        }\n    }\n\n    /**\n     * @return array<int,string>\n     */\n    protected function getCodes() : array\n    {\n        // @phpstan-ignore-next-line\n        $codes = \\array_filter((array) \\glob($this->langDir . '*'), 'is_dir');\n        $length = \\strlen($this->langDir);\n        $result = [];\n        foreach ($codes as $dir) {\n            if ($dir === false) {\n                continue;\n            }\n            $result[] = \\substr($dir, $length);\n        }\n        return $result;\n    }\n\n    /**\n     * @return array<int,string>\n     */\n    protected function getFiles(string $locale) : array\n    {\n        $filenames = App::locator()->listFiles($this->langDir . $locale);\n        foreach ($filenames as $filename) {\n            $files[] = \\substr($filename, \\strrpos($filename, \\DIRECTORY_SEPARATOR) + 1);\n        }\n        return $files ?? [];\n    }\n}\n"
  },
  {
    "path": "tests/bin/ConsoleTest.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\bin;\n\nuse Framework\\CLI\\Streams\\Stdout;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse PHPUnit\\Framework\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class ConsoleTest extends TestCase\n{\n    public function testConsole() : void\n    {\n        Stdout::init();\n        require __DIR__ . '/../../bin/console';\n        self::assertStringContainsString('about', Stdout::getContents());\n        self::assertStringContainsString('help', Stdout::getContents());\n        self::assertStringContainsString('index', Stdout::getContents());\n    }\n}\n"
  },
  {
    "path": "tests/boot/AppTest.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\boot;\n\nuse Framework\\MVC\\App;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse PHPUnit\\Framework\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class AppTest extends TestCase\n{\n    public function testApp() : void\n    {\n        $app = require __DIR__ . '/../../boot/app.php';\n        self::assertInstanceOf(App::class, $app);\n    }\n}\n"
  },
  {
    "path": "tests/boot/ConstantsTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\boot;\n\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Tests\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class ConstantsTest extends TestCase\n{\n    public function testConstants() : void\n    {\n        require_once __DIR__ . '/../../boot/constants.php';\n        self::assertIsString(ENVIRONMENT);\n        self::assertIsBool(IS_DEV);\n        self::assertDirectoryExists(ROOT_DIR);\n        self::assertDirectoryExists(APP_DIR);\n        self::assertDirectoryExists(BIN_DIR);\n        self::assertDirectoryExists(BOOT_DIR);\n        self::assertDirectoryExists(CONFIG_DIR);\n        self::assertDirectoryExists(PUBLIC_DIR);\n        self::assertDirectoryExists(STORAGE_DIR);\n        self::assertDirectoryExists(VENDOR_DIR);\n        self::assertDirectoryExists(APLUS_DIR);\n    }\n}\n"
  },
  {
    "path": "tests/boot/HelpersTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\boot;\n\nuse App;\nuse Framework\\HTTP\\Response;\nuse Framework\\HTTP\\Status;\nuse Framework\\Session\\Session;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Tests\\support\\Models\\UsersModel;\nuse Tests\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class HelpersTest extends TestCase\n{\n    public function testHelpers() : void\n    {\n        $files = helpers('tests');\n        self::assertEmpty($files);\n        $dir = \\realpath(__DIR__ . '/../support/') . \\DIRECTORY_SEPARATOR;\n        App::autoloader()->addNamespace('Support', $dir);\n        $helperPath = $dir . 'Helpers/tests.php';\n        $files = helpers('tests');\n        self::assertSame([$helperPath], $files);\n        $files = helpers(['tests']);\n        self::assertSame([$helperPath], $files);\n    }\n\n    public function testEsc() : void\n    {\n        self::assertSame('', esc(null));\n        self::assertSame('', esc(''));\n        self::assertSame('&lt;script&gt;&lt;/script&gt;', esc('<script></script>'));\n    }\n\n    public function testView() : void\n    {\n        self::assertStringContainsString(\n            'Aplus Framework',\n            view('home/index')\n        );\n    }\n\n    public function testCurrentUrl() : void\n    {\n        $this->app->runHttp('http://localhost:8080');\n        self::assertSame('http://localhost:8080/', current_url());\n    }\n\n    public function testCurrentRoute() : void\n    {\n        $this->app->runHttp('http://localhost:8080');\n        self::assertMatchedRouteName(current_route()->getName());\n    }\n\n    public function testRouteUrl() : void\n    {\n        $configs = App::config()->get('router');\n        $configs['files'][] = __DIR__ . '/../support/routes.php';\n        App::config()->set('router', $configs);\n        $this->app->runHttp('https://foo.com/users/25');\n        self::assertSame(\n            'http://localhost:8080/',\n            route_url('home.index')\n        );\n        self::assertSame(\n            'https://foo.com/',\n            route_url('test.home')\n        );\n        self::assertSame(\n            'https://foo.com/users/{int}',\n            route_url('test.users.show')\n        );\n        self::assertSame(\n            'https://foo.com/users/25',\n            route_url('test.users.show', [25])\n        );\n        self::assertSame(\n            'http://foo.com/users/13',\n            route_url('test.users.show', [13], ['http'])\n        );\n    }\n\n    public function testSession() : void\n    {\n        self::assertInstanceOf(Session::class, session());\n    }\n\n    public function testOld() : void\n    {\n        $this->setOldData();\n        self::assertNull(old('unknown'));\n        self::assertSame('', old('user'));\n        self::assertSame('John Doe', old('user[name]'));\n        self::assertSame(\n            '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;',\n            old('xss')\n        );\n        self::assertSame(\n            '<script>alert(\"xss\")</script>',\n            old('xss', false)\n        );\n    }\n\n    protected function setOldData() : void\n    {\n        App::session()->activate();\n        App::response()->redirect('/foo', [\n            'user' => [\n                'name' => 'John Doe',\n            ],\n            'xss' => '<script>alert(\"xss\")</script>',\n        ]);\n        App::session()->stop();\n    }\n\n    public function testHasOld() : void\n    {\n        $this->setOldData();\n        self::assertTrue(has_old());\n        self::assertTrue(has_old('user'));\n        self::assertTrue(has_old('user[name]'));\n        self::assertTrue(has_old('xss'));\n        self::assertFalse(has_old('foo'));\n        self::assertFalse(has_old('bar'));\n        self::assertFalse(has_old('bar[bazzz]'));\n    }\n\n    public function testHasOldWithoutRedirectData() : void\n    {\n        self::assertFalse(has_old());\n        self::assertFalse(has_old('user'));\n        self::assertFalse(has_old('user[name]'));\n        self::assertFalse(has_old('bar[bazzz]'));\n    }\n\n    public function testRedirect() : void\n    {\n        self::assertNull(App::response()->getHeader('Location'));\n        redirect('/foo');\n        self::assertSame('/foo', App::response()->getHeader('Location'));\n        redirect('/bar', ['foo' => 'Foo']);\n        self::assertSame('/bar', App::response()->getHeader('Location'));\n        self::assertSame('Foo', App::request()->getRedirectData('foo'));\n    }\n\n    public function testRedirectTo() : void\n    {\n        $configs = App::config()->get('router');\n        $configs['files'][] = __DIR__ . '/../support/routes.php';\n        App::config()->set('router', $configs);\n        $this->app->runHttp('https://foo.com/users/25');\n        $response = redirect_to('home.index');\n        self::assertSame(\n            'http://localhost:8080/',\n            $response->getHeader('Location')\n        );\n        $response = redirect_to('test.users.show');\n        self::assertSame(\n            'https://foo.com/users/{int}',\n            $response->getHeader('Location')\n        );\n        $response = redirect_to(['test.users.show', [25]]);\n        self::assertSame(\n            'https://foo.com/users/25',\n            $response->getHeader('Location')\n        );\n        $response = redirect_to(['test.users.show', [13], ['http']]);\n        self::assertSame(\n            'http://foo.com/users/13',\n            $response->getHeader('Location')\n        );\n        $response = redirect_to('home.index', ['foo' => 'bar'], Status::SEE_OTHER);\n        self::assertSame(\n            Status::SEE_OTHER,\n            $response->getStatusCode()\n        );\n        self::assertSame(\n            'http://localhost:8080/',\n            $response->getHeader('Location')\n        );\n        self::assertSame(\n            ['foo' => 'bar'],\n            $response->getRequest()->getRedirectData()\n        );\n    }\n\n    public function testCsrfInput() : void\n    {\n        self::assertStringContainsString('<input', csrf_input());\n    }\n\n    public function testRespondNotFound() : void\n    {\n        $response = respond_not_found();\n        self::assertInstanceOf(Response::class, $response);\n        self::assertStringContainsString('404', $response->getBody());\n        self::assertSame(404, $response->getStatusCode());\n        self::assertNull($response->getHeader('Content-Type'));\n    }\n\n    public function testRespondNotFoundWithContentTypeJson() : void\n    {\n        $_SERVER['HTTP_CONTENT_TYPE'] = 'application/json';\n        $response = respond_not_found();\n        self::assertInstanceOf(Response::class, $response);\n        self::assertStringContainsString('404', $response->getBody());\n        self::assertSame(404, $response->getStatusCode());\n        self::assertSame(\n            'application/json; charset=UTF-8',\n            $response->getHeader('Content-Type')\n        );\n    }\n\n    public function testRespondNotFoundWithAcceptJson() : void\n    {\n        $_SERVER['HTTP_ACCEPT'] = 'application/json';\n        $response = respond_not_found();\n        self::assertInstanceOf(Response::class, $response);\n        self::assertStringContainsString('404', $response->getBody());\n        self::assertSame(404, $response->getStatusCode());\n        self::assertSame(\n            'application/json; charset=UTF-8',\n            $response->getHeader('Content-Type')\n        );\n    }\n\n    public function testRespondNotFoundWithAcceptHtml() : void\n    {\n        $_SERVER['HTTP_ACCEPT'] = 'text/html';\n        $response = respond_not_found();\n        self::assertInstanceOf(Response::class, $response);\n        self::assertStringContainsString('404', $response->getBody());\n        self::assertSame(404, $response->getStatusCode());\n        self::assertNull($response->getHeader('Content-Type'));\n    }\n\n    public function testConfig() : void\n    {\n        $config = config('view');\n        self::assertSame(APP_DIR . 'Views', $config['base_dir']);\n        $baseDir = config('view', 'default[base_dir]');\n        self::assertSame(APP_DIR . 'Views', $baseDir);\n        $baseDir = config('view', 'default[base_dir');\n        self::assertSame(APP_DIR . 'Views', $baseDir);\n        $baseDir = config('view', 'default[base_directory');\n        self::assertNull($baseDir);\n    }\n\n    public function testModel() : void\n    {\n        self::assertInstanceOf(UsersModel::class, model(UsersModel::class));\n        self::assertSame('foo', model(UsersModel::class)->foo());\n    }\n\n    public function testEnv() : void\n    {\n        $_ENV['foo'] = 'foo';\n        $_SERVER['foo'] = 'bar';\n        $_SERVER['bar'] = 'baz';\n        self::assertSame('foo', env('foo'));\n        self::assertNull(env('bar'));\n        self::assertNull(env('unknown'));\n    }\n}\n"
  },
  {
    "path": "tests/boot/InitTest.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\boot;\n\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse PHPUnit\\Framework\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class InitTest extends TestCase\n{\n    protected function setUp() : void\n    {\n        \\ob_start();\n        require __DIR__ . '/../../boot/init.php';\n        \\ob_end_clean();\n    }\n\n    public function testErrorReporting() : void\n    {\n        if ($_SERVER['ENVIRONMENT'] === 'development') {\n            self::assertSame(-1, \\error_reporting());\n            return;\n        }\n        self::assertSame(\n            \\E_ALL & ~\\E_DEPRECATED & ~\\E_NOTICE & ~\\E_USER_DEPRECATED & ~\\E_USER_NOTICE,\n            \\error_reporting()\n        );\n    }\n\n    public function testDisplayErrors() : void\n    {\n        if ($_SERVER['ENVIRONMENT'] === 'development') {\n            self::assertSame('On', \\ini_get('display_errors'));\n            return;\n        }\n        self::assertSame('Off', \\ini_get('display_errors'));\n    }\n\n    public function testDefaultTimezone() : void\n    {\n        self::assertSame('UTC', \\date_default_timezone_get());\n    }\n}\n"
  },
  {
    "path": "tests/boot/RoutesTest.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\boot;\n\nuse Framework\\HTTP\\Status;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse Tests\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class RoutesTest extends TestCase\n{\n    public function testNotFound() : void\n    {\n        $this->app->runHttp('http://localhost:8080/foo');\n        self::assertResponseStatusCode(Status::NOT_FOUND);\n        self::assertResponseBodyContains('Error 404');\n        self::assertMatchedRouteName('collection-not-found');\n    }\n}\n"
  },
  {
    "path": "tests/config/ConfigsTest.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\config;\n\nuse PHPUnit\\Framework\\TestCase;\n\nfinal class ConfigsTest extends TestCase\n{\n    public function testConfigs() : void\n    {\n        $files = (array) \\glob(CONFIG_DIR . '*.php');\n        foreach ($files as $file) {\n            $config = require $file;\n            self::assertArrayHasKey('default', $config);\n        }\n    }\n}\n"
  },
  {
    "path": "tests/public/IndexTest.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\public;\n\nuse Framework\\HTTP\\Status;\nuse PHPUnit\\Framework\\Attributes\\RunTestsInSeparateProcesses;\nuse PHPUnit\\Framework\\TestCase;\n\n#[RunTestsInSeparateProcesses]\nfinal class IndexTest extends TestCase\n{\n    public function testIndex() : void\n    {\n        $_SERVER['REQUEST_SCHEME'] = 'http';\n        $_SERVER['HTTP_HOST'] = 'localhost:8080';\n        $_SERVER['REQUEST_URI'] = '/';\n        \\ob_start();\n        require __DIR__ . '/../../public/index.php';\n        $contents = \\ob_get_clean();\n        $headers = xdebug_get_headers();\n        self::assertNotEmpty($contents);\n        self::assertSame(Status::OK, \\http_response_code());\n        self::assertContains('Content-Type: text/html; charset=UTF-8', $headers);\n    }\n}\n"
  },
  {
    "path": "tests/support/Helpers/tests.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n"
  },
  {
    "path": "tests/support/Models/UsersModel.php",
    "content": "<?php declare(strict_types=1);\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nnamespace Tests\\support\\Models;\n\nuse Framework\\MVC\\Model;\n\nclass UsersModel extends Model\n{\n    public function foo() : string\n    {\n        return 'foo';\n    }\n}\n"
  },
  {
    "path": "tests/support/routes.php",
    "content": "<?php\n/*\n * This file is part of App Project.\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\nuse Framework\\Routing\\RouteCollection;\n\nApp::router()->serve('{scheme}://foo.com', static function (RouteCollection $routes) : void {\n    $routes->get('/', static fn () => 'Homepage', 'home');\n    $routes->get('/users/{int}', static fn () => 'Show user', 'users.show');\n}, 'test');\n"
  }
]