diff --git a/.env.example b/.env.example index c865a29..b146201 100644 --- a/.env.example +++ b/.env.example @@ -15,7 +15,7 @@ TIMEZONE="America/Sao_Paulo" # # Host, database name, user and password -DB_HOST="localhost" +DB_HOST="127.0.0.1" DB_NAME="springy" DB_USER="root" -DB_PASS="" +DB_PASS="root" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b36c486..1bcad6c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,28 +14,74 @@ permissions: jobs: tests: - runs-on: ubuntu-latest + name: php-${{ matrix.php-version }}-mysql-${{ matrix.mysql-version }} + + env: + extensions: curl, gmp, json, libxml, mbstring, mcrypt, pdo, pdo_mysql, zip + key: cache-v1 + + runs-on: ${{ matrix.os }} strategy: fail-fast: true matrix: - php: ['8.1', '8.2', '8.3'] + os: + - ubuntu-latest + php-version: ['8.2', '8.3', '8.4', '8.5'] + mysql-version: + - '8.0' - name: PHP ${{ matrix.php }} + services: + mysql: + image: mysql:${{ matrix.mysql-version }} + env: + MYSQL_DATABASE: springy + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup PHP + - name: Setup cache environment + id: cache-env + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php-version }} + extensions: ${{ env.extensions }} + key: ${{ env.key }} + + - name: Cache extensions + uses: actions/cache@v4 + with: + path: ${{ steps.cache-env.outputs.dir }} + key: ${{ steps.cache-env.outputs.key }} + restore-keys: ${{ steps.cache-env.outputs.key }} + + - name: Install PHP with extensions uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} - extensions: curl, libxml, mbstring, mcrypt + php-version: ${{ matrix.php-version }} + extensions: ${{ env.extensions }} + ini-values: date.timezone='UTC' coverage: none - # - name: Validate composer.json and composer.lock - # run: composer validate --strict + - name: Determine composer cache directory + if: matrix.os == 'ubuntu-latest' + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v4 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php-${{ matrix.php-version }}-mysql-${{ matrix.mysql-version }}-${{ hashFiles('**/composer.json') }} + restore-keys: | + php-${{ matrix.php-version }}-composer-${{ matrix.mysql-version }}- + + - name: Validate composer.json and composer.lock + run: composer validate # - name: Cache Composer packages # id: composer-cache @@ -47,10 +93,16 @@ jobs: # ${{ runner.os }}-php- - name: Install dependencies - run: composer install --prefer-dist --no-interaction --no-progress --no-suggest + run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader - name: Copy environment file run: cp .env.example .env - name: Run test suite - run: vendor/bin/phpunit \ No newline at end of file + env: + DB_HOST: "127.0.0.1" + DB_NAME: springy + DB_USER: root + DB_PASS: root + MYSQL: ${{ matrix.mysql-version }} + run: composer run test \ No newline at end of file diff --git a/.gitignore b/.gitignore index bca6e4a..158a833 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ -.env -.phpunit.result.cache +/.env +/.phpunit.result.cache +/components/* /components.lock +/node_modules/* /phpcs.log /phpmd.html -/components/* -/node_modules/* +/TODO \ No newline at end of file diff --git a/README.md b/README.md index 3c89f92..b3cbcb0 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,51 @@ A micro framework for smart PHP developers. -> **WARNING!** +> **WARNING!** > -> This project will be deprecated soon. +> This project will be deprecated soon. > -> The new major release 5 is under development. +> The new major release 5 is under development. > -> It will be not compatible with version 4, but comes with several new features and enhancements. +> It may be not compatible with version 4, but comes with several new features +> and enhancements. > -> You can follow new project at https://github.com/springy-framework +> You can follow new project at https://github.com/springy-framework ## What is this -Springy is a micro [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) framework for coders who work with the [PHP](http://www.php.net) script language to develop web applications. +Springy is a micro +[MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) +framework for coders who work with the [PHP](http://www.php.net) script language +to develop web applications. -It was created based upon the KISS principle in which the simplicity is the base to good things. +It was created based upon the KISS principle in which the simplicity is the base +to good things. ## Requirements -* PHP 8.1+ -* [Composer](https://getcomposer.org/) Dependency Manager for PHP +- PHP 8.2+ +- [Composer](https://getcomposer.org/) Dependency Manager for PHP ## Language dependencies -* [Multibyte String](http://php.net/manual/en/intro.mbstring.php) library -* [Mcrypt](http://php.net/manual/en/intro.mcrypt.php) library +- [Multibyte String](http://php.net/manual/en/intro.mbstring.php) library +- [Mcrypt](http://php.net/manual/en/intro.mcrypt.php) library ## How do I start? Read the documentation: -* [English](/documentation/en/README.md) -* [Portuguese](/documentation/pt/README.md) +- [English](/documentation/en/README.md) +- [Portuguese](/documentation/pt/README.md) ## Contributing -1. Fork it! -2. Create your feature branch: `git checkout -b my-branch` -3. Commit your changes: `git commit -m 'Description of your feature'` -4. Push to the branch: `git push origin my-feature` -5. Submit a pull request. +1. Fork it! +2. Create your feature branch: `git checkout -b my-branch` +3. Commit your changes: `git commit -m 'Description of your feature'` +4. Push to the branch: `git push origin my-feature` +5. Submit a pull request. ## License diff --git a/cmd b/cmd index 0df6fb0..4d7f66d 100644 --- a/cmd +++ b/cmd @@ -7,14 +7,12 @@ * @copyright 2007 Fernando Val * @author Fernando Val * - * @version 3.0.1 - * - * @codingStandardsIgnoreFile + * @version 3.1.0 */ define('SPRINGY_START', microtime(true)); -require __DIR__ . '/consts'; +require __DIR__ . '/consts.php'; // Loads the Composer autoload require __DIR__ . '/vendor/autoload.php'; @@ -29,9 +27,6 @@ if ($argc < 2) { exit(999); } -// Load framework configuration -$sysconf = file_exists(web_root() . '/sysconf.php') ? require_once web_root() . '/sysconf.php' : []; - // Define error handlers error_reporting(E_ALL); set_exception_handler('springyExceptionHandler'); @@ -70,4 +65,4 @@ while (++$arg < $argc) { } ob_start(); -Springy\Kernel::run($sysconf); +Springy\Kernel::run(); diff --git a/composer.json b/composer.json index d17310e..0a150c1 100644 --- a/composer.json +++ b/composer.json @@ -33,22 +33,25 @@ } }, "require-dev": { - "squizlabs/php_codesniffer": "^3.5", "phpmd/phpmd": "@stable", - "phpunit/phpunit": "~10.5" + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^4.0" }, "require": { "php": ">=8.1", + "ext-ctype": "*", + "ext-gmp": "*", + "ext-hash": "*", + "ext-intl": "*", + "ext-mbstring": "*", + "ext-session": "*", - "smarty/smarty": "^5.4", - "twig/twig": "^2.15", - "ircmaxell/password-compat": "^1.0", "matthiasmullie/minify": "^1.3", - "sendgrid/sendgrid": "^7.8", - "phpmailer/phpmailer": "^6.0", - "vlucas/phpdotenv": "^5.6", - - "components/jquery": "~3.1" + "phpmailer/phpmailer": "^6.10", + "sendgrid/sendgrid": "^8.1", + "smarty/smarty": "^5.8", + "twig/twig": "^3.28", + "vlucas/phpdotenv": "^5.6" }, "autoload": { "files": ["springy/Core/helpers.php"], @@ -65,7 +68,6 @@ "phpmd" : "phpmd . html codesize.xml > phpmd.html", "test": [ "@phpcs", - "@phpmd", "@phpunit" ], "read-me": "Adds @components post-install-cmd and post-update-cmd if needed" diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 58fa88c..0000000 --- a/composer.lock +++ /dev/null @@ -1,3722 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "16e5b0a15682ed6a6893af62d1634ab8", - "packages": [ - { - "name": "components/jquery", - "version": "v3.7.1", - "source": { - "type": "git", - "url": "https://github.com/components/jquery.git", - "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/components/jquery/zipball/8edc7785239bb8c2ad2b83302b856a1d61de60e7", - "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7", - "shasum": "" - }, - "type": "component", - "extra": { - "component": { - "files": [ - "jquery.min.js", - "jquery.min.map", - "jquery.slim.js", - "jquery.slim.min.js", - "jquery.slim.min.map" - ], - "scripts": [ - "jquery.js" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "JS Foundation and other contributors" - } - ], - "description": "jQuery JavaScript Library", - "homepage": "http://jquery.com", - "support": { - "forum": "http://forum.jquery.com", - "irc": "irc://irc.freenode.org/jquery", - "issues": "https://github.com/jquery/jquery/issues", - "source": "https://github.com/jquery/jquery", - "wiki": "http://docs.jquery.com/" - }, - "time": "2023-09-22T01:43:46+00:00" - }, - { - "name": "graham-campbell/result-type", - "version": "v1.1.3", - "source": { - "type": "git", - "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" - }, - "type": "library", - "autoload": { - "psr-4": { - "GrahamCampbell\\ResultType\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - } - ], - "description": "An Implementation Of The Result Type", - "keywords": [ - "Graham Campbell", - "GrahamCampbell", - "Result Type", - "Result-Type", - "result" - ], - "support": { - "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", - "type": "tidelift" - } - ], - "time": "2024-07-20T21:45:45+00:00" - }, - { - "name": "ircmaxell/password-compat", - "version": "v1.0.4", - "source": { - "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "autoload": { - "files": [ - "lib/password.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@php.net", - "homepage": "http://blog.ircmaxell.com" - } - ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", - "keywords": [ - "hashing", - "password" - ], - "support": { - "issues": "https://github.com/ircmaxell/password_compat/issues", - "source": "https://github.com/ircmaxell/password_compat/tree/v1.0" - }, - "time": "2014-11-20T16:49:30+00:00" - }, - { - "name": "matthiasmullie/minify", - "version": "1.3.73", - "source": { - "type": "git", - "url": "https://github.com/matthiasmullie/minify.git", - "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/cb7a9297b4ab070909cefade30ee95054d4ae87a", - "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "matthiasmullie/path-converter": "~1.1", - "php": ">=5.3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": ">=2.0", - "matthiasmullie/scrapbook": ">=1.3", - "phpunit/phpunit": ">=4.8", - "squizlabs/php_codesniffer": ">=3.0" - }, - "suggest": { - "psr/cache-implementation": "Cache implementation to use with Minify::cache" - }, - "bin": [ - "bin/minifycss", - "bin/minifyjs" - ], - "type": "library", - "autoload": { - "psr-4": { - "MatthiasMullie\\Minify\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matthias Mullie", - "email": "minify@mullie.eu", - "homepage": "https://www.mullie.eu", - "role": "Developer" - } - ], - "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", - "homepage": "https://github.com/matthiasmullie/minify", - "keywords": [ - "JS", - "css", - "javascript", - "minifier", - "minify" - ], - "support": { - "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.73" - }, - "funding": [ - { - "url": "https://github.com/matthiasmullie", - "type": "github" - } - ], - "time": "2024-03-15T10:27:10+00:00" - }, - { - "name": "matthiasmullie/path-converter", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/matthiasmullie/path-converter.git", - "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", - "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "MatthiasMullie\\PathConverter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matthias Mullie", - "email": "pathconverter@mullie.eu", - "homepage": "http://www.mullie.eu", - "role": "Developer" - } - ], - "description": "Relative path converter", - "homepage": "http://github.com/matthiasmullie/path-converter", - "keywords": [ - "converter", - "path", - "paths", - "relative" - ], - "support": { - "issues": "https://github.com/matthiasmullie/path-converter/issues", - "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" - }, - "time": "2019-02-05T23:41:09+00:00" - }, - { - "name": "phpmailer/phpmailer", - "version": "v6.9.3", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", - "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "doctrine/annotations": "^1.2.6 || ^1.13.3", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^9.3.5", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.7.2", - "yoast/phpunit-polyfills": "^1.0.4" - }, - "suggest": { - "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "ext-openssl": "Needed for secure SMTP sending and DKIM signing", - "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" - }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "support": { - "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" - }, - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ], - "time": "2024-11-24T18:04:13+00:00" - }, - { - "name": "phpoption/phpoption", - "version": "1.9.3", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpOption\\": "src/PhpOption/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" - }, - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "support": { - "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", - "type": "tidelift" - } - ], - "time": "2024-07-20T21:41:07+00:00" - }, - { - "name": "sendgrid/php-http-client", - "version": "3.14.4", - "source": { - "type": "git", - "url": "https://github.com/sendgrid/php-http-client.git", - "reference": "6d589564522be290c7d7c18e51bcd8b03aeaf0b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sendgrid/php-http-client/zipball/6d589564522be290c7d7c18e51bcd8b03aeaf0b6", - "reference": "6d589564522be290c7d7c18e51bcd8b03aeaf0b6", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "php": ">=5.6" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "phpunit/phpunit": "^5.7 || ^6.5", - "sebastian/version": "^1.0.6", - "squizlabs/php_codesniffer": "~2.0" - }, - "suggest": { - "composer/ca-bundle": "Including this library will ensure that a valid CA bundle is available for secure connections" - }, - "type": "library", - "autoload": { - "psr-4": { - "SendGrid\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matt Bernier", - "email": "mbernier@twilio.com" - }, - { - "name": "Elmer Thomas", - "email": "ethomas@twilio.com" - } - ], - "description": "HTTP REST client, simplified for PHP", - "homepage": "http://github.com/sendgrid/php-http-client", - "keywords": [ - "api", - "fluent", - "http", - "rest", - "sendgrid" - ], - "support": { - "issues": "https://github.com/sendgrid/php-http-client/issues", - "source": "https://github.com/sendgrid/php-http-client/tree/3.14.4" - }, - "time": "2022-03-09T20:21:55+00:00" - }, - { - "name": "sendgrid/sendgrid", - "version": "7.11.5", - "source": { - "type": "git", - "url": "https://github.com/sendgrid/sendgrid-php.git", - "reference": "1d2fd3b72687fe82264853a8888b014f8f99e81f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sendgrid/sendgrid-php/zipball/1d2fd3b72687fe82264853a8888b014f8f99e81f", - "reference": "1d2fd3b72687fe82264853a8888b014f8f99e81f", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-openssl": "*", - "php": ">=5.6", - "sendgrid/php-http-client": "~3.10", - "starkbank/ecdsa": "0.*" - }, - "replace": { - "sendgrid/sendgrid-php": "*" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.9 || ^6.4.3", - "squizlabs/php_codesniffer": "3.*", - "swaggest/json-diff": "^3.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "SendGrid\\Mail\\": "lib/mail/", - "SendGrid\\Stats\\": "lib/stats/", - "SendGrid\\Helper\\": "lib/helper/", - "SendGrid\\Contacts\\": "lib/contacts/", - "SendGrid\\EventWebhook\\": "lib/eventwebhook/" - }, - "classmap": [ - "lib/BaseSendGridClientInterface.php", - "lib/SendGrid.php", - "lib/TwilioEmail.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "This library allows you to quickly and easily send emails through Twilio SendGrid using PHP.", - "homepage": "http://github.com/sendgrid/sendgrid-php", - "keywords": [ - "email", - "grid", - "send", - "sendgrid", - "twilio sendgrid" - ], - "support": { - "issues": "https://github.com/sendgrid/sendgrid-php/issues", - "source": "https://github.com/sendgrid/sendgrid-php/tree/7.11.5" - }, - "time": "2022-03-09T20:21:56+00:00" - }, - { - "name": "smarty/smarty", - "version": "v5.4.3", - "source": { - "type": "git", - "url": "https://github.com/smarty-php/smarty.git", - "reference": "c6bff5795081ca5e60aabda59fb87daa511acd1e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/smarty-php/smarty/zipball/c6bff5795081ca5e60aabda59fb87daa511acd1e", - "reference": "c6bff5795081ca5e60aabda59fb87daa511acd1e", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-mbstring": "^1.27" - }, - "require-dev": { - "phpunit/phpunit": "^8.5 || ^7.5", - "smarty/smarty-lexer": "^4.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Smarty\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0" - ], - "authors": [ - { - "name": "Monte Ohrt", - "email": "monte@ohrt.com" - }, - { - "name": "Uwe Tews", - "email": "uwe.tews@googlemail.com" - }, - { - "name": "Rodney Rehm", - "email": "rodney.rehm@medialize.de" - }, - { - "name": "Simon Wisselink", - "homepage": "https://www.iwink.nl/" - } - ], - "description": "Smarty - the compiling PHP template engine", - "homepage": "https://smarty-php.github.io/smarty/", - "keywords": [ - "templating" - ], - "support": { - "forum": "https://github.com/smarty-php/smarty/discussions", - "issues": "https://github.com/smarty-php/smarty/issues", - "source": "https://github.com/smarty-php/smarty/tree/v5.4.3" - }, - "time": "2024-12-23T00:38:44+00:00" - }, - { - "name": "starkbank/ecdsa", - "version": "0.0.5", - "source": { - "type": "git", - "url": "https://github.com/starkbank/ecdsa-php.git", - "reference": "484bedac47bac4012dc73df91da221f0a66845cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/starkbank/ecdsa-php/zipball/484bedac47bac4012dc73df91da221f0a66845cb", - "reference": "484bedac47bac4012dc73df91da221f0a66845cb", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/ellipticcurve.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "StarkBank", - "email": "developers@starkbank.com", - "homepage": "https://starkbank.com", - "role": "Developer" - } - ], - "description": "fast openSSL-compatible implementation of the Elliptic Curve Digital Signature Algorithm (ECDSA)", - "homepage": "https://github.com/starkbank/ecdsa-php", - "support": { - "issues": "https://github.com/starkbank/ecdsa-php/issues", - "source": "https://github.com/starkbank/ecdsa-php/tree/v0.0.5" - }, - "time": "2021-06-06T22:24:49+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", - "reference": "fa2ae56c44f03bed91a39bfc9822e31e7c5c38ce", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "metapackage", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "twig/twig", - "version": "v2.16.1", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "19185947ec75d433a3ac650af32fc05649b95ee1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/19185947ec75d433a3ac650af32fc05649b95ee1", - "reference": "19185947ec75d433a3ac650af32fc05649b95ee1", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.8" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.16-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.16.1" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2024-09-09T17:53:56+00:00" - }, - { - "name": "vlucas/phpdotenv", - "version": "v5.6.1", - "source": { - "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", - "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-filter": "*", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" - }, - "suggest": { - "ext-filter": "Required to use the boolean validator." - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "5.6-dev" - } - }, - "autoload": { - "psr-4": { - "Dotenv\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Vance Lucas", - "email": "vance@vancelucas.com", - "homepage": "https://github.com/vlucas" - } - ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", - "keywords": [ - "dotenv", - "env", - "environment" - ], - "support": { - "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", - "type": "tidelift" - } - ], - "time": "2024-07-20T21:52:34+00:00" - } - ], - "packages-dev": [ - { - "name": "composer/pcre", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.13.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-02-12T12:17:51+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.4.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" - }, - "time": "2024-12-30T11:07:19+00:00" - }, - { - "name": "pdepend/pdepend", - "version": "2.16.2", - "source": { - "type": "git", - "url": "https://github.com/pdepend/pdepend.git", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", - "shasum": "" - }, - "require": { - "php": ">=5.3.7", - "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.19" - }, - "require-dev": { - "easy-doc/easy-doc": "0.0.0|^1.2.3", - "gregwar/rst": "^1.0", - "squizlabs/php_codesniffer": "^2.0.0" - }, - "bin": [ - "src/bin/pdepend" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "PDepend\\": "src/main/php/PDepend" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Official version of pdepend to be handled with Composer", - "keywords": [ - "PHP Depend", - "PHP_Depend", - "dev", - "pdepend" - ], - "support": { - "issues": "https://github.com/pdepend/pdepend/issues", - "source": "https://github.com/pdepend/pdepend/tree/2.16.2" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", - "type": "tidelift" - } - ], - "time": "2023-12-17T18:09:59+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpmd/phpmd", - "version": "2.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpmd/phpmd.git", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", - "shasum": "" - }, - "require": { - "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", - "ext-xml": "*", - "pdepend/pdepend": "^2.16.1", - "php": ">=5.3.9" - }, - "require-dev": { - "easy-doc/easy-doc": "0.0.0 || ^1.3.2", - "ext-json": "*", - "ext-simplexml": "*", - "gregwar/rst": "^1.0", - "mikey179/vfsstream": "^1.6.8", - "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" - }, - "bin": [ - "src/bin/phpmd" - ], - "type": "library", - "autoload": { - "psr-0": { - "PHPMD\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Manuel Pichler", - "email": "github@manuel-pichler.de", - "homepage": "https://github.com/manuelpichler", - "role": "Project Founder" - }, - { - "name": "Marc Würth", - "email": "ravage@bluewin.ch", - "homepage": "https://github.com/ravage84", - "role": "Project Maintainer" - }, - { - "name": "Other contributors", - "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", - "role": "Contributors" - } - ], - "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", - "homepage": "https://phpmd.org/", - "keywords": [ - "dev", - "mess detection", - "mess detector", - "pdepend", - "phpmd", - "pmd" - ], - "support": { - "irc": "irc://irc.freenode.org/phpmd", - "issues": "https://github.com/phpmd/phpmd/issues", - "source": "https://github.com/phpmd/phpmd/tree/2.15.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", - "type": "tidelift" - } - ], - "time": "2023-12-11T08:22:20+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:31:57+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.45", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8", - "reference": "bd68a781d8e30348bc297449f5234b3458267ae8", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.3", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.2", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.0", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2025-02-06T16:08:12+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:12:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:58:43+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:59:15+00:00" - }, - { - "name": "sebastian/comparator", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-10-18T14:56:07+00:00" - }, - { - "name": "sebastian/complexity", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:37:17+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:15:17+00:00" - }, - { - "name": "sebastian/environment", - "version": "6.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-23T08:47:14+00:00" - }, - { - "name": "sebastian/exporter", - "version": "5.1.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:17:12+00:00" - }, - { - "name": "sebastian/global-state", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:19:19+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:38:20+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:08:32+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:06:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:05:40+00:00" - }, - { - "name": "sebastian/type", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:10:45+00:00" - }, - { - "name": "sebastian/version", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-07T11:34:05+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.11.3", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", - "reference": "ba05f990e79cbe69b9f35c8c1ac8dca7eecc3a10", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" - }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-01-23T17:04:15+00:00" - }, - { - "name": "symfony/config", - "version": "v6.4.14", - "source": { - "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<5.4", - "symfony/service-contracts": "<2.5" - }, - "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v6.4.14" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-04T11:33:53+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v6.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "b343c3b2f1539fe41331657b37d5c96c1d1ea842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/b343c3b2f1539fe41331657b37d5c96c1d1ea842", - "reference": "b343c3b2f1539fe41331657b37d5c96c1d1ea842", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.2.10|^7.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.1", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<6.3", - "symfony/yaml": "<5.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" - }, - "require-dev": { - "symfony/config": "^6.1|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-02-20T10:02:49+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v6.4.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-25T15:07:50+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.5-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/var-exporter", - "version": "v6.4.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "be6e71b0c257884c1107313de5d247741cfea172" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/be6e71b0c257884c1107313de5d247741cfea172", - "reference": "be6e71b0c257884c1107313de5d247741cfea172", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "lazy-loading", - "proxy", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-02-13T09:33:32+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:36:25+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": { - "phpmd/phpmd": 0 - }, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" -} diff --git a/conf/database.php b/conf/database.php new file mode 100644 index 0000000..370786d --- /dev/null +++ b/conf/database.php @@ -0,0 +1,127 @@ + 'mysql', + + /* + * Path to JSON structure files directory. + * + * Default: 'app/Stuctures' + */ + 'model_structures' => __DIR__ . '/../app/Structures', + + /* + * Connections. + */ + 'connections' => [ + /* + * MySQL sample configuration connection. + */ + 'mysql' => [ + /* + * Driver class. + */ + 'driver' => MySQL::class, + + /* + * Server host. + */ + 'host' => env('DB_HOST'), + + /* + * Database user name. + */ + 'username' => env('DB_USER'), + + /* + * Databae user password. + */ + 'password' => env('DB_PASS'), + + /* + * Database name. + */ + 'database' => env('DB_NAME'), + + /* + * Database collations. + */ + 'charset' => 'utf8mb4', + + /* + * Database persistent connection. + */ + 'persistent' => false, + + /* + * Migration scripts directory. + */ + 'migration' => [ + 'dir' => __DIR__ . '/../migrations/mysql', + 'namespace' => 'App', + ], + ], + + /* + * PostgreSQL sample configuration connection. + */ + 'postgres' => [ + /* + * Driver name. + */ + 'driver' => PostgreSQL::class, + + /* + * Server host. + */ + 'host' => env('DB_HOST', 'localhost'), + + /* + * Database user name. + */ + 'username' => env('DB_USER', 'pgsql'), + + /* + * Databae user password. + */ + 'password' => env('DB_PASS', '123'), + + /* + * Database name. + */ + 'database' => env('DB_NAME', 'test'), + + /* + * Database schema + */ + 'schema' => env('DB_SCHEMA', 'public'), + ], + + /* + * SQLite sample configuration connectio. + */ + 'sqlite' => [ + /* + * Driver name. + */ + 'driver' => SQLite::class, + + /* + * Database file. + */ + 'database' => env('DB_NAME', ':memory:'), + + /* + * Foreign key constraints. + */ + 'foreign_key_constraints' => '', + ], + ], +]; diff --git a/conf/development/db.php b/conf/development/db.php index 1e6c277..2629b53 100644 --- a/conf/development/db.php +++ b/conf/development/db.php @@ -9,11 +9,6 @@ */ return [ - 'round_robin' => [ - 'type' => 'file', - 'server_addr' => var_dir(), - 'server_port' => 11211, - ], 'cache' => [ 'type' => 'off', 'server_addr' => '127.0.0.1', diff --git a/conf/development/mail.php b/conf/development/mail.php deleted file mode 100644 index 265e3b5..0000000 --- a/conf/development/mail.php +++ /dev/null @@ -1,20 +0,0 @@ - 'phpmailer-class', - 'mailers' => [ - 'phpmailer-class' => [ - 'username' => 'you@gmail.com', - 'password' => 'put-your-password-here', - 'debug' => 0, - ], - ], -]; diff --git a/conf/mail.php b/conf/mail.php index d8b9aad..7b5ad58 100644 --- a/conf/mail.php +++ b/conf/mail.php @@ -7,14 +7,97 @@ */ return [ - // Mail to notify system errors (used by framework) + /* + * Mailer driver class name. + * + * Options: + * - Springy\Mail\Drivers\PhpMailer::class + * - Springy\Mail\Drivers\SendGrid::class + */ + 'driver' => Springy\Mail\Drivers\PhpMailer::class, + + /* + * Errors-To header value. + */ 'errors_go_to' => '', + /* + * Fake e-mail destination. + * + * Tells the application to send all messages to a certain e-mail + * address instead of the actual recipient. + */ + 'fake_to' => env('FAKE_MAIL_TO', ''), + + /* + * Settings for mailer driver. + */ + 'settings' => [ + /* + * The SendGrid API key. + * + * @see https://sendgrid.com/docs/for-developers/sending-email/api-getting-started/ + */ + 'apikey' => env('SENDGRID_API_KEY'), + + /* + * The SendGrid options. + */ + 'options' => [ + 'protocol' => 'https', + 'raise_exceptions' => false, + 'turn_off_ssl_verification' => false, + ], + + /* + * The mail transfer protocol. + */ + 'protocol' => 'smtp', + + /* + * The SMTP host. + */ + 'host' => 'your.smtp.host.addr', + + /* + * The SMTP port. + */ + 'port' => 587, + + /* + * The SMTP cryptography protocol. + */ + 'cryptography' => 'tls', + + /* + * SMTP authentication needed. + */ + 'authenticated' => true, + + /* + * The SMTP user. + */ + 'username' => env('SMTP_USER', 'or-put-your-smtp-user-here'), + + /* + * The SMTP password. + */ + 'password' => env('SMTP_PASS', 'or-put-your-smtp-password-here'), + + /* + * Debug level. + * + * Values accepted: 0, 1 or 2 + */ + 'debug' => 0, + ], + // System Admin (used by framework) 'system_adm_mail' => 'noreply@yourdomain.com', 'system_adm_name' => 'System Admin', - // The mailer system + // Old mailer configuration + // @deprecated 4.7.0 'default_driver' => 'phpmailer-class', 'mailers' => [ // Sample for PHP Mailer Class @@ -42,19 +125,6 @@ 'turn_off_ssl_verification' => false, ], ], - // Sample for Sendgrid API using auth method - 'sendgrid-auth' => [ - 'driver' => 'sendgrid', - 'username' => 'put-your-sendgrid-user-here', - 'password' => 'put-your-sendgrid-pass-here', - 'options' => [ - 'protocol' => 'https', - // 'host' => 'smtp.sendgrid.net', - // 'port' => 465, - 'raise_exceptions' => false, - 'turn_off_ssl_verification' => false, - ], - ], // Sample using Manuel Lemos' Mime Message class (NOT IMPLEMENTED) 'mimemessage' => [ 'driver' => 'mimemessage', diff --git a/conf/production/db.php b/conf/production/db.php index e80b10f..29c77d4 100644 --- a/conf/production/db.php +++ b/conf/production/db.php @@ -9,11 +9,6 @@ */ return [ - 'round_robin' => [ - 'type' => 'memcached', - 'server_addr' => 'youmemcachedserver.localnetwork', - 'server_port' => 11211, - ], 'cache' => [ 'type' => 'off', 'server_addr' => 'youmemcachedserver.localnetwork', diff --git a/conf/production/mail.php b/conf/production/mail.php deleted file mode 100644 index 0c1d70b..0000000 --- a/conf/production/mail.php +++ /dev/null @@ -1,18 +0,0 @@ - 'sendgrid-api', - 'mailers' => [ - 'sendgrid-api' => [ - 'apikey' => 'put-the-sendgrid-api-key-here', - ], - ], -]; diff --git a/conf/uri.php b/conf/uri.php index 47623b2..b56dd7b 100644 --- a/conf/uri.php +++ b/conf/uri.php @@ -28,6 +28,14 @@ 'api' => 'App\\Api', ], + /* + * Fallback to index controller if the segments do not match any + * controller. + * + * @var bool + */ + 'fallbackToIndex' => false, + /* * Routing configuration by HTTP host. * @@ -42,6 +50,7 @@ 'segments' => [ 'api' => 'App\\Local\\Api', ], + 'fallbackToIndex' => false, 'template' => ['$admin'], ], // Command line controllers @@ -49,6 +58,7 @@ 'module' => '', 'namespace' => 'App\\Console', 'segments' => [], + 'fallbackToIndex' => false, 'template' => [], ], ], diff --git a/consts b/consts.php similarity index 100% rename from consts rename to consts.php diff --git a/documentation/HISTORY.md b/documentation/HISTORY.md index 42e8263..0a31223 100644 --- a/documentation/HISTORY.md +++ b/documentation/HISTORY.md @@ -2,32 +2,62 @@ ## Version 4 +### 4.7.0 + +* Adjusted compatibility to PHP >= 8.2 +* Updated `twig/twig` dependency to version 3.28 +* Added `array_dotted_set()` helper function +* Added funcion `Springy\DB::isConnected()` +* Added bitwise comparison to `Springy\DB\Conditions` class +* Added `uri.routing.fallbackToIndex` boolean configuration +* Added `uri.routing.hosts.*.fallbackToIndex` boolean configuration +* The funcion `Springy\DB::connect()` changed to set internal identifier and + return void +* The funcion `Springy\DB::beginTransaction()` is no longer static and no + longer receive parameter +* The funcion `Springy\DB::commit()` is no longer static and no longer + receive parameter +* Deprecated funcion `Springy\DB::connected()`. Use DB::isConnected instead +* The funcion `Springy\DB::rollBack()` is no longer static +* Deprecated 'pool' round robin database connection configuration +* Deprecated `Springy\DB::makeDbDateTime()` function +* Deprecated `Springy\Utils\UUID` class +* Removed class `Springy\Utils\Strings_ANSI` +* Removed class `Springy\Utils\Strings_UTF8` +* Removed the `db.round_robin` configuration +* Removed `Springy\DB::disconnect()` funcion +* Removed deprecated `Springy\DB::castDateBrToDb()` funcion +* Removed deprecated `Springy\DB::castDateDbToBr()` funcion +* Removed deprecated `Springy\DB::longBrazilianDate()` funcion +* Removed `ircmaxell/password-compat` component dependency + ### 4.6.2 -* Debug JavaScript updated +* Debug JavaScript updated. * Added command line script to run PHPUnit tests on all supported PHP versions using Docker containers. ### 4.6.1 * Added functions: - * `Kernel::getTemplatePrefix()` - * `Kernel::setTemplatePrefix()` + * `Springy\Kernel::getTemplatePrefix()` + * `Springy\Kernel::setTemplatePrefix()` * Added `template.smarty_extensions` (`array`) configuration to implements support to custom Smarty extensions * Fixed automatic controller routing system -* Fixed `Security\AclManager::getAclObjectName()` function +* Fixed `Springy\Security\AclManager::getAclObjectName()` function * Use of MemcacheD as session driver redesigned * Copyright page moved out to a HTML file * Support to Smarty template updated to version 5 -* Deprecated `Kernel::controllerRoot()` function -* Deprecated `Kernel::VERSION` constant -* Removed undocumented parameter from `URI::relativePathPage()` function -* Removed third parameter from `URI::getSegment()` function +* Deprecated `Springy\Kernel::controllerRoot()` function +* Deprecated `Springy\Kernel::VERSION` constant +* Removed undocumented parameter from `Springy\URI::relativePathPage()` + function +* Removed third parameter from `Springy\URI::getSegment()` function * Removed functions and methods: - * `Session::_saveDbSession()` - * `Session::_saveMcSession()` - * `URI::checkRedirect()` + * `Springy\Session::_saveDbSession()` + * `Springy\Session::_saveMcSession()` + * `Springy\URI::checkRedirect()` * Removed configurations: * `template.template_plugins_path` * `uri.prevalidate_controller` @@ -87,7 +117,7 @@ 'ENVIRONMENT_ALIAS' key from `sysconf.php`. * Added optional second parameters to `config_get()` helper function to defines a default value; -* Added PHPUnit test for `Utils\JSON` class; +* Added PHPUnit test for `Springy\Utils\JSON` class; * Helpers functions script moved from web root directory to `springy/Core` directory; * Migration script `public/migration.php` moved to root directory and renamed @@ -96,37 +126,39 @@ `cmd`; * `Kernel::controllerNamespace()` function now is readonly; * `URI::parseURI()` function now returns void; -* Rafactored `Core\Debug` class; +* Rafactored `Springy\Core\Debug` class; * Deprecated `sysconf.php` configuration file on web root directory. Use `consts` and `.env` files in project root. * Deprecated controllers loading without namespace and with '.page.php' extension for previously routing discovery; * Deprecated `_global.php` pre-controller load; * Deprecated classes: - * `DeepDir`; - * `Soap_Client` - uses `Springy\Utils\SoapClient`; + * `Springy\DeepDir`; + * `Springy\Soap_Client` - uses `Springy\Utils\SoapClient`; * `Springy\Security\AclUserInterface`; * Deprecated constants: - * `Kernel::PATH_APPLICATION` (see `app_path()` helper function); - * `Kernel::PATH_CLASSES`; - * `Kernel::PATH_CONF` (see `config_dir()` helper function); - * `Kernel::PATH_CONTROLLER`; - * `Kernel::PATH_LIBRARY`; - * `Kernel::PATH_MIGRATION` (see `migration_dir()` helper function); - * `Kernel::PATH_PROJECT` (see `project_path()` helper function); - * `Kernel::PATH_VAR` (see `var_dir()` helper function); - * `Kernel::PATH_VENDOR`; - * `Kernel::PATH_WEB_ROOT`; + * `Springy\Kernel::PATH_APPLICATION` (see `app_path()` helper function); + * `Springy\Kernel::PATH_CLASSES`; + * `Springy\Kernel::PATH_CONF` (see `config_dir()` helper function); + * `Springy\Kernel::PATH_CONTROLLER`; + * `Springy\Kernel::PATH_LIBRARY`; + * `Springy\Kernel::PATH_MIGRATION` (see `migration_dir()` helper + function); + * `Springy\Kernel::PATH_PROJECT` (see `project_path()` helper function); + * `Springy\Kernel::PATH_VAR` (see `var_dir()` helper function); + * `Springy\Kernel::PATH_VENDOR`; + * `Springy\Kernel::PATH_WEB_ROOT`; * Deprecated functions: * `sysconf()` - use `env()` helper function; - * `Kernel::charset()` - use `env('CHARSET')` helper function; - * `Kernel::path()` - use apropriated helper function or constant; - * `Kernel::projectCodeName()` - use `app_codename()` helper function; - * `Kernel::systemName()` - use `app_name()` helper function; - * `Kernel::systemConfGlobal()` - use `env()` helper function; - * `Kernel::systemVersion()` - use `app_version()` helper function; - * `Session::_saveDbSession()` - use `Session::saveToDB()`; - * `Session::_saveMcSession()` - use `Session::saveToMC()`; + * `Springy\Kernel::charset()` - use `env('CHARSET')` helper function; + * `Springy\Kernel::path()` - use apropriated helper function or constant; + * `Springy\Kernel::projectCodeName()` - use `app_codename()` helper + function; + * `Springy\Kernel::systemName()` - use `app_name()` helper function; + * `Springy\Kernel::systemConfGlobal()` - use `env()` helper function; + * `Springy\Kernel::systemVersion()` - use `app_version()` helper function; + * `Springy\Session::_saveDbSession()` - use `Session::saveToDB()`; + * `Springy\Session::_saveMcSession()` - use `Session::saveToMC()`; * Removed configuration keys from `sysconf.php` (will be ignored if exists): * 'ACTIVE_ENVIRONMENT' - set the value in SPRINGY_ENVIRONMENT key at the .env file; @@ -147,26 +179,27 @@ * Deprecated support to suffix '.conf' and '.default.conf' on configuration files; * Removed constants: - * `Kernel::PATH_CONFIGURATION`; - * `Kernel::PATH_ROOT`; - * `Kernel::PATH_SYSTEM`; - * `Kernel::PATH_CLASS`; + * `Springy\Kernel::PATH_CONFIGURATION`; + * `Springy\Kernel::PATH_ROOT`; + * `Springy\Kernel::PATH_SYSTEM`; + * `Springy\Kernel::PATH_CLASS`; * Removed functions and methods: * `springyAutoload` autoload function; - * `Controller::_authorizationCheck()` deprecated at version 4.5; - * `Controller::_forbidden()` deprecated at version 4.5; - * `Controller::_pageNotFound()` deprecated at version 4.5; - * `Controller::_redirect()` deprecated at version 4.5; - * `Controller::_template()` deprecated at version 4.5; - * `Controller::_userSpecialVerifications()` deprecated at version 4.5; - * `Utils\JSON::getDados()` deprecated at version 4.4; - * `Utils\JSON::printJ()` deprecated at version 4.4; + * `Springy\Controller::_authorizationCheck()` deprecated at version 4.5; + * `Springy\Controller::_forbidden()` deprecated at version 4.5; + * `Springy\Controller::_pageNotFound()` deprecated at version 4.5; + * `Springy\Controller::_redirect()` deprecated at version 4.5; + * `Springy\Controller::_template()` deprecated at version 4.5; + * `Springy\Controller::_userSpecialVerifications()` deprecated at version + 4.5; + * `Springy\Utils\JSON::getDados()` deprecated at version 4.4; + * `Springy\Utils\JSON::printJ()` deprecated at version 4.4; * `with()` helper function; * Removed classes: - * `CreditCardValidation`; - * `Log`; - * `Utils\Excel`; - * `Utils\ZipFile`; + * `Springy\CreditCardValidation`; + * `Springy\Log`; + * `Springy\Utils\Excel`; + * `Springy\Utils\ZipFile`; * Removed configuratios: * `uri.host_controller_path`; * `uri.redirects`; @@ -191,12 +224,12 @@ * Fixed bugs to `Migrator` an `Errors` class; * Added `Kernel::systemConfGlobal()` to get global system configuration data; * `Kernel` functions updated to readonly mode: - * `Kernel::charset()`; - * `Kernel::environment()`; - * `Kernel::path()`; - * `Kernel::projectCodeName()`; - * `Kernel::systemName()`; - * `Kernel::systemVersion()`; + * `Springy\Kernel::charset()`; + * `Springy\Kernel::environment()`; + * `Springy\Kernel::path()`; + * `Springy\Kernel::projectCodeName()`; + * `Springy\Kernel::systemName()`; + * `Springy\Kernel::systemVersion()`; * Support to `PHPMailer` upgraded to v6.x; * Added GitHub Actions test case; * Deprecated indexes from `sysconf.php` configuration file: @@ -205,44 +238,44 @@ * 'SPRINGY_PATH'; * 'VENDOR_PATH'; * Deprecated classes: - * `CreditCardValidation`; - * `Log`; - * `Utils\Excel`; - * `Utils\ZipFile`; + * `Springy\CreditCardValidation`; + * `Springy\Log`; + * `Springy\Utils\Excel`; + * `Springy\Utils\ZipFile`; * Deprecated constants: - * `Kernel::PATH_CONFIGURATION`; - * `Kernel::PATH_SYSTEM` constant; - * `Kernel::PATH_CLASS` constant; + * `Springy\Kernel::PATH_CONFIGURATION`; + * `Springy\Kernel::PATH_SYSTEM` constant; + * `Springy\Kernel::PATH_CLASS` constant; * Deprecated functions: * `springyAutoload` autoload function; - * `Controller::_authorizationCheck()`; - * `Controller::_forbidden()`; - * `Controller::_pageNotFound()`; - * `Controller::_redirect()`; - * `Controller::_template()`; - * `Controller::_userSpecialVerifications()`; - * `Container\DIContainer::has()`; - * `Container\DIContainer::resolve()`; - * `DB::castDateBrToDb()`; - * `DB::castDateDbToBr()`; - * `DB::longBrazilianDate()`; - * `Error::handler()`; - * `Events\Mediator::on()`; - * `Events\Mediator::off()`; - * `Security\AclManager::setDefaultModule()`; - * `Security\AclManager::getDefaultModule()`; - * `Strings::cep()`; - * `Strings::data()`; - * `Strings::guid()`; - * `Strings::hora()`; - * `Strings::numero()`; - * `Strings::sizeMatch()`; - * `Strings::telefone()`; -* Removed `SOAP` class; + * `Springy\Controller::_authorizationCheck()`; + * `Springy\Controller::_forbidden()`; + * `Springy\Controller::_pageNotFound()`; + * `Springy\Controller::_redirect()`; + * `Springy\Controller::_template()`; + * `Springy\Controller::_userSpecialVerifications()`; + * `Springy\Container\DIContainer::has()`; + * `Springy\Container\DIContainer::resolve()`; + * `Springy\DB::castDateBrToDb()`; + * `Springy\DB::castDateDbToBr()`; + * `Springy\DB::longBrazilianDate()`; + * `Springy\Error::handler()`; + * `Springy\Events\Mediator::on()`; + * `Springy\Events\Mediator::off()`; + * `Springy\Security\AclManager::setDefaultModule()`; + * `Springy\Security\AclManager::getDefaultModule()`; + * `Springy\Strings::cep()`; + * `Springy\Strings::data()`; + * `Springy\Strings::guid()`; + * `Springy\Strings::hora()`; + * `Springy\Strings::numero()`; + * `Springy\Strings::sizeMatch()`; + * `Springy\Strings::telefone()`; +* Removed `Springy\SOAP` class; * Removed functions (already deprecated): - * `URI::_GET()`; - * `URI::getHost()`; - * `Cookie::del()`; + * `Springy\URI::_GET()`; + * `Springy\URI::getHost()`; + * `Springy\Cookie::del()`; * Updated error HTML asset files; * Removed required suffix '.conf' and '.default.conf' from configuration files; @@ -256,15 +289,15 @@ ### 4.4.0 -* Added support to `Controller` suffix without underscore to controller names - to help in convertion to new namespace method; -* Added support to `App\Controller` namespace without `Controller` or +* Added support to `Springy\Controller` suffix without underscore to + controller names to help in convertion to new namespace method; +* Added support to `Springy\App\Controller` namespace without `Controller` or `_Controller` name suffix to controllers; * Deprecated `.class` suffix for application classes; * Deprecated `.static` suffix for application classes; * Deprecated application classes with hiphen in its name; * Deprecated `with()` helper function; -* Deprecated `URI::_GET()` function; +* Deprecated `Springy\URI::_GET()` function; * Adds functions without underscore prefix to deprecate old standard functions; * Adds support to bootstrap application script as an alternative to deprecated @@ -280,14 +313,14 @@ * Hook controllers signed as deprecated; * Empty function `_ignore_global` to ignore Global pre-controller deprecated; * Removed the following classes: - * `DBDelete`; - * `DBExpression`; - * `DBFiltro`; - * `DBInsert`; - * `DBSelect`; - * `DBUpdate`; - * `DBWhere`; -* Removed `Pagination` class; + * `Springy\DBDelete`; + * `Springy\DBExpression`; + * `Springy\DBFiltro`; + * `Springy\DBInsert`; + * `Springy\DBSelect`; + * `Springy\DBUpdate`; + * `Springy\DBWhere`; + * `Springy\Pagination`; * Deleted Doxygen documentation. ### 4.3.1 diff --git a/documentation/en/README.md b/documentation/en/README.md index 9cd47f7..d471df8 100644 --- a/documentation/en/README.md +++ b/documentation/en/README.md @@ -59,15 +59,15 @@ are required to reproduce the same effect. ### Configuration -The `consts` file defines the application name, version and project code name, -as well as application directory paths. +The `consts.php` file defines the application name, version and project code +name, as well as application directory paths. The `.env` file defines your application's configuration entries. This file should not be added to your repository. Create it from the existing `.env.example` file in the project root. All other configuration stays in files inside the folder defined by `CONFIG_DIR` -constant defined in `consts` file. +constant defined in `consts.php` file. #### Configuration files diff --git a/documentation/en/consts.md b/documentation/en/consts.md new file mode 100644 index 0000000..cefc79e --- /dev/null +++ b/documentation/en/consts.md @@ -0,0 +1,23 @@ +# consts.php - The main application configuration file + +## Definition + +The *consts.php* file is the general configuration script for the application. + +This script sets application configurations in `Kernel` class. + +## Constants + +* **'APP_NAME'** - A string with the name of the application; +* **'APP_VERSION'** - The application version. See https://semver.org/ +* **'APP_CODE_NAME'** - A string with application code name. +* **'APP_PATH'** - The application path. +* **'CONFIG_DIR'** - Configuration system path. +* **'MIGRATION_DIR'** - Path for the folder of the scripts with database + struture changes. +* **'PROJECT_ROOT'** - The application root directory. +* **'VAR_DIR'** - The var path where the application will save temporary and + cache files. +* **'CACHE_DIR'** - Path to cache directory. +* **'WEB_ROOT'** - Web root directory path. + diff --git a/documentation/en/sysconf.md b/documentation/en/sysconf.md deleted file mode 100644 index 6d43630..0000000 --- a/documentation/en/sysconf.md +++ /dev/null @@ -1,26 +0,0 @@ -# sysconf.php - The main application configuration file - -***WARNING!*** THIS FILE WAS DEPRECATED. THE FRAMEWORK YET LOAD IT IF EXISTS IN -4.6.x VERSION. BUT IN VERSION 4.7 OR GREATER THIS FILE WILL BE IGNORED. - -## Definition - -The *sysconf.php* file is the general configuration script for the application. - -This script sets application configurations in `Kernel` class. - -## Structure - -The SYSTEM variable is an array with the following structure of indexes: - -* **'SYSTEM_NAME'** - A string with the name of the application; -* **'SYSTEM_VERSION'** - The application version. A string or an array with 3 - indexes of the version: major, minor and release. Example: `[1, 0, 0]` -* **'PROJECT_CODE_NAME'** - A string with application code name. -* **'PROJECT_PATH'** - The application root directory. -* **'CONFIG_PATH'** - Configuration system path. -* **'APP_PATH'** - The application path. -* **'VAR_PATH'** - The var path where the application will save temporary and - cache files. -* **'MIGRATION_PATH'** - Path for the folder of the scripts with database - struture changes. diff --git a/documentation/pt/README.md b/documentation/pt/README.md index 3c2c89d..74e0ad4 100644 --- a/documentation/pt/README.md +++ b/documentation/pt/README.md @@ -60,10 +60,10 @@ configurações são necessárias para ter o mesmo efeito. ### Configuração -O script *sysconf.php* é o arquivo de configuração geral do sistema. Nele estão -entradas de definição como nome e versão do sistema, ambiente, árvore de -diretórios da aplicação, charset e timezone. Mais detalhes -[aqui](/documentation/pt/sysconf.md). +O script de constantes *consts.php* é o arquivo de configuração geral do +sistema. Nele estão entradas de definição como nome e versão do sistema, +ambiente, árvore de diretórios da aplicação, charset e timezone. Mais detalhes +[aqui](/documentation/pt/consts.md). As demais configurações da aplicação deverão estar no diretório e sub-diretórios definidos pela constante `CONFIG_DIR`. diff --git a/documentation/pt/conf/db.md b/documentation/pt/conf/db.md index d804ec2..eae9438 100644 --- a/documentation/pt/conf/db.md +++ b/documentation/pt/conf/db.md @@ -18,14 +18,12 @@ usadas como nomes de conexão. São elas: - **'default'** - conexão padrão; - **'cache'** - configuração de cache de banco em MemcacheD; -- **'round_robin'** - configuração de round robin de banco. ### Entradas de configuração de conexão: - **'database_type'** - Determina o tipo de banco de dados. Veja "Tipos de banco de dados suportados"; -- **'host_name'** - Nome do host. Se database_type for 'pool', essa entrada -deve ser um array contendo das entradas de configuração do pool de conexão. +- **'host_name'** - Nome do host. - **'user_name'** - Nome do usuário - **'password'** - Senha de acesso - **'database'** - Banco ou schema @@ -37,9 +35,6 @@ deve ser um array contendo das entradas de configuração do pool de conexão. - **'mysql'** - Bancos de dados MySQL; - **'pgsql'** ou **'postgresql'** - Banco de dados PostgreSQL; - **'sqlite'** - Banco de dados SQLite; -- **'pool'** - Configuração para pool de conexão de banco de dados por round -robin. Nesse caso, o **'host_name'** deverá ser um array com as entradas de -configuração. ### Entradas da configuração de cache de consulta em MemcacheD: @@ -47,10 +42,3 @@ configuração. - **'server_addr'** - Endereço do servidor memcached ou caminho do arquivo de controle; - **'server_port'** - Porta do servidor memcached. - -### Entradas da configuração de pool por round_robin: -; -- **'type'** - Tipo de controle do round robin (file | memcached); -- **'server_addr'** - Endereço do servidor memcached ou caminho do arquivo de -controle; -- **'server_port'** - Porta do servidor memcached. diff --git a/migrate b/migrate index 4c06d23..17c9b09 100644 --- a/migrate +++ b/migrate @@ -7,20 +7,15 @@ * @copyright 2007 Fernando Val * @author Fernando Val * - * @version 0.5.1 - * - * @codingStandardsIgnoreFile + * @version 0.6.0 */ define('SPRINGY_START', microtime(true)); -require __DIR__ . '/consts'; +require __DIR__ . '/consts.php'; // Loads the Composer autoload require __DIR__ . '/vendor/autoload.php'; -// Load framework configuration -$sysconf = file_exists(web_root() . '/sysconf.php') ? require_once web_root() . '/sysconf.php' : []; - // Define error handlers error_reporting(E_ALL); set_exception_handler('springyExceptionHandler'); @@ -41,4 +36,4 @@ set_exception_handler('springyExceptionHandler'); set_error_handler('springyErrorHandler'); ob_start(); -Springy\Kernel::run($sysconf); +Springy\Kernel::run(); diff --git a/phpcs.xml b/phpcs.xml index 4ef581e..cdc7d72 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,8 +1,11 @@ - Coding standard for Boobam project + Coding standard for Springy project ./springy + ./cmd + ./migrate + ./www/_Main.php ./tests ./var @@ -15,14 +18,6 @@ - - - - - \ No newline at end of file diff --git a/springy/Container/DIContainer.php b/springy/Container/DIContainer.php index ff74bc7..b578a29 100644 --- a/springy/Container/DIContainer.php +++ b/springy/Container/DIContainer.php @@ -6,8 +6,6 @@ * @copyright 2015 Fernando Val * @author Fernando Val * @author Allan Marques - * - * @version 0.2.2 */ namespace Springy\Container; @@ -16,9 +14,6 @@ use Closure; use InvalidArgumentException; -/** - * DIContainer class. - */ class DIContainer implements ArrayAccess { // Type constants @@ -26,19 +21,14 @@ class DIContainer implements ArrayAccess public const TYPE_PARAM = 'param'; public const TYPE_SHARED = 'shared'; - /** @var array saves keys of all elements in container */ - protected $registeredKeys; + // Keys of all elements in container + protected array $registeredKeys; - /** @var array saves all parameters */ - protected $params; - /** @var array saves all factories */ - protected $factories; - /** @var array saves all factory extensions */ - protected $factoriesExtensions; - /** @var arry saves all shared instances */ - protected $sharedInstances; - /** @var array saves factories that will be shared instances (lazy load) */ - protected $sharedInstancesFactories; + protected array $params; + protected array $factories; + protected array $factoriesExtensions; + protected array $sharedInstances; + protected array $sharedInstancesFactories; // Factories that will be shared instances (lazy load) public function __construct() { @@ -53,12 +43,11 @@ public function __construct() /** * Registers a parameter. * - * @param string|Closure $key - * @param mixed $value any type except object. + * The value can be any type except object. * - * @return mixed + * @throws InvalidArgumentException if value is an object. */ - public function raw($key, $value = null): mixed + public function raw(string|Closure $key, mixed $value = null): mixed { // If key is a closure returns its result (useful in array mode) if ($key instanceof Closure) { @@ -84,30 +73,19 @@ public function raw($key, $value = null): mixed /** * Returns the param registered with given key. * - * @param mixed $key - * * @throws InvalidArgumentException if parameter $key not exists in container. - * - * @return mixed */ - public function param($key): mixed + public function param(string $key): mixed { - if (isset($this->params[$key])) { - return $this->params[$key]; - } - - throw new InvalidArgumentException("The '{$key}' key was not registered as a param."); + return $this->params[$key] ?? throw new InvalidArgumentException( + "The '{$key}' key was not registered as a param." + ); } /** * Registers a factory (Closure). * * Useful to save complex objects creator functions. - * - * @param string $key - * @param Closure $factory - * - * @return void */ public function bind(string $key, Closure $factory): void { @@ -118,12 +96,7 @@ public function bind(string $key, Closure $factory): void /** * Executes a factory and returs its result. * - * @param string $key - * @param array $params - * * @throws InvalidArgumentException if factory not exists. - * - * @return mixed */ public function make(string $key, array $params = []): mixed { @@ -150,12 +123,7 @@ public function make(string $key, array $params = []): mixed /** * Registers an extension into a factory. * - * @param string $key - * @param Closure $extension - * * @throws InvalidArgumentException if factory $key not exists. - * - * @return void */ public function extend(string $key, Closure $extension): void { @@ -173,10 +141,8 @@ public function extend(string $key, Closure $extension): void * @param Closure|object $instance * * @throws InvalidArgumentException if $instance if not a closure or object. - * - * @return mixed */ - public function instance($key, $instance = null): mixed + public function instance(string|Closure $key, ?object $instance = null): mixed { // If key is a closure executes it and returns the result (useful in array mode) if ($key instanceof Closure) { @@ -190,8 +156,6 @@ public function instance($key, $instance = null): mixed $this->sharedInstancesFactories[$key] = $instance; return null; - } elseif (!is_object($instance)) { - throw new InvalidArgumentException('The argument passed is not an instance of an object.'); } $this->sharedInstances[$key] = $instance; @@ -202,61 +166,25 @@ public function instance($key, $instance = null): mixed /** * Returns a shared instance identified by $key. * - * @param string $key - * * @throws InvalidArgumentException if instance not exists. * * @return object */ - public function shared(string $key) + public function shared(string $key): object { // If has a lazy load executes it. if (isset($this->sharedInstancesFactories[$key])) { - $this->sharedInstances[$key] = call_user_func($this->sharedInstancesFactories[$key], $this); + $this->instance($key, call_user_func($this->sharedInstancesFactories[$key], $this)); unset($this->sharedInstancesFactories[$key]); } - if (isset($this->sharedInstances[$key])) { - return $this->sharedInstances[$key]; - } - - throw new InvalidArgumentException("The '{$key}' key was not registered as a shared instance."); - } - - /** - * Same as offsetExists (deprecated). - * - * @param string $key - * - * @return bool - * - * @deprecated 4.5.0 - */ - public function has(string $key): bool - { - return $this->offsetExists($key); - } - - /** - * Same as offsetGet (deprecated). - * - * @param string $key - * - * @return mixed - * - * @deprecated 4.5.0 - */ - public function resolve(string $key) - { - return $this->offsetGet($key); + return $this->sharedInstances[$key] ?? throw new InvalidArgumentException( + "The '{$key}' key was not registered as a shared instance." + ); } /** * Same as offsetUnset (deprecated). - * - * @param string $key - * - * @return void */ public function forget(string $key): void { @@ -274,13 +202,14 @@ public function offsetGet($offset): mixed throw new InvalidArgumentException("The '{$offset}' key was not registered as a dependency."); } - $getter = [ - self::TYPE_FACTORY => fn ($key) => $this->make($key), - self::TYPE_SHARED => fn ($key) => $this->shared($key), - self::TYPE_PARAM => fn ($key) => $this->param($key), - ]; - - return call_user_func($getter[$this->registeredKeys[$offset]], $offset); + return call_user_func( + match ($this->registeredKeys[$offset]) { + self::TYPE_FACTORY => fn ($key) => $this->make($key), + self::TYPE_SHARED => fn ($key) => $this->shared($key), + self::TYPE_PARAM => fn ($key) => $this->param($key), + }, + $offset + ); } public function offsetSet($offset, $value): void @@ -304,30 +233,28 @@ public function offsetSet($offset, $value): void public function offsetUnset($offset): void { - $unset = [ - self::TYPE_FACTORY => function ($key) { - unset($this->factories[$key]); - unset($this->factoriesExtensions[$key]); + call_user_func( + match ($this->registeredKeys[$offset] ?? '') { + self::TYPE_FACTORY => function ($key) { + unset($this->factories[$key]); + unset($this->factoriesExtensions[$key]); + }, + self::TYPE_SHARED => function ($key) { + unset($this->sharedInstances[$key]); + }, + self::TYPE_PARAM => function ($key) { + unset($this->params[$key]); + }, + default => fn () => null, }, - self::TYPE_SHARED => function ($key) { - unset($this->sharedInstances[$key]); - }, - self::TYPE_PARAM => function ($key) { - unset($this->params[$key]); - }, - ]; - - if (isset($unset[$this->registeredKeys[$offset] ?? ''])) { - call_user_func($unset[$this->registeredKeys[$offset]], $offset); - } + $offset + ); unset($this->registeredKeys[$offset]); } /** * Returns a new instance of this class. - * - * @return self */ public static function newInstance(): self { diff --git a/springy/Controller.php b/springy/Controller.php index 8b86b04..b59146e 100644 --- a/springy/Controller.php +++ b/springy/Controller.php @@ -8,32 +8,35 @@ * @copyright 2016-2018 Fernando Val * @author Allan Marques * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 1.1.0 */ namespace Springy; +use Springy\Exceptions\HttpErrorForbidden; +use Springy\Exceptions\HttpErrorNotFound; use Springy\Security\AclManager; class Controller extends AclManager { - /** @var bool Define if the controller is restricted to signed in users. */ - protected $authNeeded = false; - /** - * @var bool|string|array Define a URL to redirect the user if it is not signed ($authNeeded must be true). - * Can be a string or an array used by URI::buildUrl(); - **/ - protected $redirectUnsigned = false; - - /** @var Template|null The template object */ - protected $template = null; - /** @var bool Define if the template's page must be cached. */ - protected $tplIsCached = false; - /** @var int Define the live time (in seconds) of the cache. */ - protected $tplCacheTime = 1800; // 30 minutes default - /// Define an identificator to the template cache. - protected $tplCacheId = null; + /** Define if the controller is restricted to signed in users. */ + protected bool $authNeeded = false; + /** Define a URL to redirect the user if it is not signed ($authNeeded must be true). */ + protected array $redirectUnsigned = [ + 'enabled' => false, + 'segments' => [], + 'query' => [], + 'forceRewrite' => false, + 'host' => 'dynamic', + ]; + + /** The template object */ + protected ?Template $template = null; + /** Define if the template's page must be cached. */ + protected bool $tplIsCached = false; + /** Define the live time (in seconds) of the cache. */ + protected int $tplCacheTime = 1800; // 30 minutes default + /**Define an identificator to the template cache. */ + protected ?string $tplCacheId = null; /** * The constructor method. @@ -43,32 +46,28 @@ class Controller extends AclManager */ public function __construct() { - if (app('user.auth.manager')->check()) { - parent::__construct(app('user.auth.manager')->user()); - } else { - parent::__construct(app('user.auth.identity')); - } + parent::__construct( + app('user.auth.manager')->check() + ? app('user.auth.manager')->user() + : app('user.auth.identity') + ); - // Do nothing if is free for unsigned users if (!$this->authNeeded) { + // Do nothing if is free for unsigned users return; - } - - // Verify if is an authenticated user - if ($this->user->isLoaded()) { - // Call user special verifications - if (!$this->userSpecialVerifications()) { - $this->forbidden(); - } - - // Check if the controller and respective method is permitted to the user - $this->authorizationCheck(); + } elseif (!$this->user->isLoaded()) { + // Has no user logged in then kill the application with the 403 forbidden page. + $this->forbidden(); return; } - // Kill the application with the 403 forbidden page. - $this->forbidden(); + // Call user special verifications then... + $this->userSpecialVerifications() + // check if the controller and respective method is permitted to the user + ? $this->authorizationCheck() + // or kill with the 403 forbidden page + : $this->forbidden(); } /** @@ -114,40 +113,32 @@ protected function createTemplate($template = null): void /** * Sends a "403 - Forbidden" error and kill the application. */ - protected function forbidden() + protected function forbidden(): never { - if ($this->redirectUnsigned) { - $this->redirect( - ( - is_array($this->redirectUnsigned) - && isset($this->redirectUnsigned['segments']) - && isset($this->redirectUnsigned['query']) - && isset($this->redirectUnsigned['forceRewrite']) - && isset($this->redirectUnsigned['host']) - ) ? URI::buildURL( - $this->redirectUnsigned['segments'], - $this->redirectUnsigned['query'], - $this->redirectUnsigned['forceRewrite'], - $this->redirectUnsigned['host'] - ) : URI::buildURL($this->redirectUnsigned) - ); - } - - new Errors(403, 'Forbidden'); + $this->redirectUnsigned['enabled'] + ? $this->redirect( + URI::buildURL( + $this->redirectUnsigned['segments'] ?? [], + $this->redirectUnsigned['query'] ?? [], + $this->redirectUnsigned['forceRewrite'] ?? false, + $this->redirectUnsigned['host'] ?? 'dynamic' + ) + ) + : throw new HttpErrorForbidden(); } /** * Sends a "404 - Page not found" error and kill the application. */ - protected function pageNotFound(): void + protected function pageNotFound(): never { - new Errors(404, 'Page not found'); + throw new HttpErrorNotFound(); } /** * Sends a URL redirect to the user browser and kill the application. */ - protected function redirect($url): void + protected function redirect($url): never { URI::redirect($url); } diff --git a/springy/Core/ErrorsList.php b/springy/Core/ErrorsList.php new file mode 100644 index 0000000..99e04d9 --- /dev/null +++ b/springy/Core/ErrorsList.php @@ -0,0 +1,133 @@ +errorTable = config_get('system.system_error.table_name') ?: '_system_errors'; + $this->dbConnection = new DB($dbServer); + + if (!$this->dbConnection->isConnected()) { + throw_error(500, 'Fail to connect to database'); + } + } + + public function __invoke() + { + $errorCode = URI::getSegment(1, false); + + match (true) { + $errorCode === false => $this->printErrorsList(), + preg_match('/^\\w{8}$/', $errorCode) === 1 => $this->printErrorDetails($errorCode), + $errorCode === 'delete' => $this->delete(), + default => throw new HttpErrorNotFound(), + }; + } + + private function delete(): void + { + $errorCode = URI::getSegment(2, false); + + if ($errorCode == 'all') { + $this->dbConnection->execute('DELETE FROM ' . $this->errorTable, []); + echo 'ALL errors deleted from error log.'; + + return; + } + + $idList = explode(',', $errorCode); + $this->dbConnection->execute( + 'DELETE FROM ' . $this->errorTable + . ' WHERE error_code ' . ( + count($idList) > 1 + ? 'in (' . implode(',', array_fill(0, count($idList), '?')) . ')' + : '= ?' + ), + count($idList) > 1 ? $idList : [$errorCode] + ); + echo 'Error(s) ID ' . $errorCode . ' deleted from log.'; + } + + private function getErrorDetail(array $error): array + { + $json = json_decode($error['details']); + + if (!json_last_error()) { + $error['details'] = $json; + } + + return $error; + } + + private function getTemplate(string $template): string + { + $output = file_get_contents($template); + $output = str_replace('{systemName}', app_name(), $output); + $output = str_replace('{sistemVersion}', app_version(), $output); + + return $output; + } + + private function header(): void + { + header('Content-type: text/html; charset=UTF-8', true, 200); + header('X-Content-Type-Options: nosniff'); + header('X-XSS-Protection: 1; mode=block'); + header('X-Frame-Options: SAMEORIGIN'); + header('Referrer-Policy: no-referrer'); + header('Permissions-Policy: interest-cohort=()'); + header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); + header('Pragma: no-cache'); + header('Expires: 0'); + } + + private function printErrorDetails(string $errorCode): void + { + $this->dbConnection->execute( + 'SELECT * FROM ' . $this->errorTable . ' WHERE error_code = ?', + [$errorCode] + ); + + $error = $this->dbConnection->fetchNext(); + + if (!$error) { + throw new HttpErrorNotFound(); + } + + $output = $this->getTemplate(__DIR__ . DS . 'assets' . DS . 'error-details.html'); + $this->header(); + echo str_replace('{errorData}', json_encode($this->getErrorDetail($error)), $output); + } + + /** + * Prints the error log content. + */ + private function printErrorsList(): void + { + $order_column = URI::getParam('orderBy') ?: 'last_time'; + $order_type = URI::getParam('sort') ?: 'DESC'; + $this->dbConnection->execute( + 'SELECT id, error_code, description, occurrences, last_time, details' . + ' FROM ' . $this->errorTable . + ' ORDER BY ' . $order_column . ' ' . $order_type + ); + $errList = array_map( + fn ($row) => $this->getErrorDetail($row), + $this->dbConnection->fetchAll() + ); + + $output = $this->getTemplate(__DIR__ . DS . 'assets' . DS . 'errors-list.html'); + $this->header(); + echo str_replace('{errorsList}', json_encode($errList), $output); + } +} diff --git a/springy/Core/Input.php b/springy/Core/Input.php index 3495047..521346b 100644 --- a/springy/Core/Input.php +++ b/springy/Core/Input.php @@ -1,13 +1,11 @@ * @author Fernando Val - * - * @version 0.3.1 */ namespace Springy\Core; @@ -96,7 +94,7 @@ public function isPost() public function isAjax() { return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && - strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + mb_strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; } /** diff --git a/springy/Core/assets/debug.html b/springy/Core/assets/debug.html index 0d8512c..aa4aa25 100644 --- a/springy/Core/assets/debug.html +++ b/springy/Core/assets/debug.html @@ -253,10 +253,12 @@ .spring-debug-backtrace-data.ajax * { color: #fff; } - .spring-debug-backtrace-data.ajax > pre { + .spring-debug-backtrace-data.ajax > textarea { + display: block; + max-width: 100%; + min-width: 100%; + height: 100px; padding: 6px; - max-height: 150px; - overflow: auto; background-color: #1C2529; color: #fff; } @@ -372,7 +374,7 @@ // Clears debug informations const clearDebug = () => { - document.getElementById('springyDebugData').innerHTML = ''; + document.getElementById('springyDebugData').innerText = ''; document.getElementById('springyDebugBox').classList.remove('has-data'); }; @@ -454,7 +456,7 @@ let title = 'AJAX result'; - time.innerHTML = 'Time: ' + + time.innerText = 'Time: ' + (now.getHours() < 10 ? '0' : '') + now.getHours() + ':' + (now.getMinutes() < 10 ? '0' : '') + now.getMinutes() + ':' + (now.getSeconds() < 10 ? '0' : '') + now.getSeconds() + ''; @@ -486,15 +488,15 @@ content.append(`Parameters: ${param}`); } - template.querySelector('.springy-debug-value').innerHTML = title; + template.querySelector('.springy-debug-value').innerText = title; template.querySelector('.springy-debug-value').appendChild(content); // Content if (txt) { if (typeof txt === 'object') { - const data = document.createElement('pre'); + const data = document.createElement('textarea'); - data.innerHTML = JSON.stringify(txt, null, '\t'); + data.value = JSON.stringify(txt, null, '\t'); template.querySelector('.spring-debug-backtrace-data') .append(data); @@ -503,7 +505,7 @@ } } else { template.querySelector('.spring-debug-backtrace-data') - .innerHTML = txt; + .innerText = txt; } } @@ -562,7 +564,7 @@ }); // Observe all AJAX requests (jQuery) - $(window).on('ajaxComplete', function (e, XMLHttpRequest, ajaxOptions) { + $(window).bind('ajaxComplete', function (e, XMLHttpRequest, ajaxOptions) { const p = { url: ajaxOptions.url, params: decodeURI(ajaxOptions.data) @@ -575,7 +577,7 @@ json = XMLHttpRequest.responseJSON; } else { try { - json = JSON.parse(XMLHttpRequest.responseText); + json = $.parseJSON(XMLHttpRequest.responseText); } catch (e) { printAjaxResults('AJAX Result', p, XMLHttpRequest.responseText); @@ -596,7 +598,7 @@ let json; try { - json = JSON.parse(this.responseText); + json = $.parseJSON(this.responseText); } catch (e) { printAjaxResults('XMLHttpRequest Result', p, this.responseText); diff --git a/springy/Core/assets/error-details.html b/springy/Core/assets/error-details.html new file mode 100644 index 0000000..68652f6 --- /dev/null +++ b/springy/Core/assets/error-details.html @@ -0,0 +1,148 @@ + + + + + + + {systemName} v{sistemVersion} - Occurrence Errors + + + + + + +
+

System Errors

+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/springy/Core/assets/errors-list.html b/springy/Core/assets/errors-list.html new file mode 100644 index 0000000..87490ad --- /dev/null +++ b/springy/Core/assets/errors-list.html @@ -0,0 +1,141 @@ + + + + + + + {systemName} v{sistemVersion} - Occurrence Errors + + + + + + +
+

System Errors

+ + + + + + + + + + + + + + + + +
#ErrorDescriptionFileLineOccurrences
+
+ + + + + \ No newline at end of file diff --git a/springy/Core/debug_template.html b/springy/Core/debug_template.html deleted file mode 100644 index d785ef1..0000000 --- a/springy/Core/debug_template.html +++ /dev/null @@ -1,322 +0,0 @@ - - - -
- -
DEBUG
-
diff --git a/springy/Core/helpers.php b/springy/Core/helpers.php index 2a50f37..a1aefd5 100644 --- a/springy/Core/helpers.php +++ b/springy/Core/helpers.php @@ -8,10 +8,11 @@ * @copyright 2014 Fernando Val * @author Allan Marques * @author Fernando Val - * - * @version 5.0.0 */ +use Springy\Exceptions\HttpError; +use Springy\Exceptions\SpringyException; + // Definig the constantes if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); @@ -93,7 +94,7 @@ function app_path(): string */ function app_version(): string { - return defined('APP_VERSION') ? APP_VERSION : Springy\Kernel::systemVersion(); + return defined('APP_VERSION') ? APP_VERSION : '0.0.0'; } /** @@ -105,11 +106,25 @@ function app_version(): string * * @return mixed */ -function array_dotted_get(array $array, string $key, $default = null) +function array_dotted_get(array $array, string $key, $default = null): mixed { return Springy\Utils\ArrayUtils::newInstance()->dottedGet($array, $key, $default); } +/** + * Gets a key into the array using dotted notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * + * @return mixed + */ +function array_dotted_set(array &$array, string $key, mixed $value): void +{ + Springy\Utils\ArrayUtils::newInstance()->dottedSet($array, $key, $value); +} + /** * Returns a URL string using URI::buildUrl() function. * @@ -188,6 +203,8 @@ function cookie_get(string $name): mixed /** * A var_dump and die help function. * + * @SuppressWarnings(PHPMD.ExitExpression) + * * @param mixed $var the variable or value to be sent to standard output. * @param bool $die a boolen flag to determine if system die after print the value of $var. * @@ -459,11 +476,11 @@ function sysconf($key): mixed * @param int $status * @param string $message * - * @return void + * @return never */ -function throw_error($status = 500, $message = 'Internal Server Error'): void +function throw_error($status = 500, $message = 'Internal Server Error'): never { - new Springy\Errors($status, $message); + throw new SpringyException($message, $status); } /** @@ -475,7 +492,10 @@ function throw_error($status = 500, $message = 'Internal Server Error'): void */ function springyExceptionHandler(Throwable $error) { - (new Springy\Errors())->process($error, 500); + (new Springy\Errors())->process( + $error, + $error instanceof HttpError ? $error->getCode() : 500 + ); } /** @@ -484,11 +504,16 @@ function springyExceptionHandler(Throwable $error) function springyErrorHandler($errno, $errstr, $errfile, $errline) { (new Springy\Errors())->process( - new Springy\Exceptions\SpringyException($errstr, $errno, null, $errfile, $errline), + new SpringyException($errstr, $errno, null, $errfile, $errline), 500 ); } +function url(string $host): string +{ + return config_get('uri.' . $host, $host); +} + /** * Returns var files directory path. * diff --git a/springy/DB.php b/springy/DB.php index bc82e0a..93b2b33 100644 --- a/springy/DB.php +++ b/springy/DB.php @@ -8,27 +8,23 @@ * @author Lucas Cardozo * @author Allan Marques * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 1.10.2 */ namespace Springy; use Exception; +use PDO; +use PDOException; use Springy\Core\Debug; use Springy\Exceptions\SpringyException; -/** - * Relational database access class. - */ class DB { - /// Guarda os IDs de conexão com os SGBDs - private static $conectionIds = []; + // connection instances + private static array $conectionIds = []; + /// SQL Resource private $resSQL = null; - /// Tempo em segundos da validade do cache - private $cacheExpires = null; /// Cache dos registros private $cacheStatement = null; /// Último comando executado @@ -41,10 +37,8 @@ class DB private $sqlErrorInfo = null; /// Contador de comandos SQL executados private static $sqlNum = 0; - /// Recurso de conexão atual - private $dataConnect = false; - /// Entrada de configuração de banco atual - private $database = false; + // Datanase connection resource + private PDO|null $dataConnect; /// Flag de habilitação do relatório de erros private $reportError = true; /// Flag do modo debug @@ -55,14 +49,14 @@ class DB /** * Constructor. * - * @param string $database DB configuration key. + * @param string $dbIdentifier database configuration identifier. * @param int|null $cacheExpires cached query expiration time in seconds. */ - public function __construct($database = 'default', $cacheExpires = null) - { - $this->cacheExpires = $cacheExpires; - $this->database = $database; - $this->dataConnect = $this->connect($this->database); + public function __construct( + private string $dbIdentifier = 'default', + private ?int $cacheExpires = null + ) { + $this->connect($this->dbIdentifier); } /** @@ -70,82 +64,87 @@ public function __construct($database = 'default', $cacheExpires = null) */ public function __destruct() { - if ($this->resSQL === null) { - return; + if (!is_null($this->resSQL)) { + $this->resSQL->closeCursor(); + $this->resSQL = null; } + } - $this->resSQL->closeCursor(); - $this->resSQL = null; + /** + * Checks if there is a connection. + * + * @throws Exception + */ + private function checkConnection(): void + { + if (is_null($this->dataConnect)) { + trigger_error('No connection to database.', E_USER_ERROR); + } } /** * Connects to the DBMS. * - * @param string $database DB configuration key. - * - * @return PDO|bool + * @param string $identifier DB configuration key. */ - public function connect($database) + public function connect(string $identifier): void { - if (isset(self::$conErrors[$database])) { - return false; - } + $this->dataConnect = null; + $this->dbIdentifier = $identifier; + + if (isset(self::$conErrors[$identifier])) { + return; + } elseif ((self::$conectionIds[$identifier] ?? null) instanceof PDO) { + $this->dataConnect = self::$conectionIds[$identifier]; - // Verifica se a instância já está definida e conectada - if (isset(self::$conectionIds[$database])) { - return self::$conectionIds[$database]['PDO']; + return; } - // Lê as configurações de acesso ao banco de dados - $conf = Configuration::get('db', $database); + $conf = Configuration::get('db', $identifier); - // Verifica se o servidor é um pool (round robin) - if ($conf['database_type'] == 'pool' && is_array($conf['host_name'])) { - return $this->roundRobinConnect($database, $conf); + if (!$conf['host_name'] || !$conf['database']) { + $this->reportError('Hostname or database not defined.'); } + // PDO configuration $pdoConf = [ - \PDO::ATTR_CASE => \PDO::CASE_NATURAL, - // \PDO::ATTR_EMULATE_PREPARES => false, - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL, - \PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_CASE => PDO::CASE_NATURAL, + // PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_PERSISTENT => $conf['persistent'] ?? true, ]; - if ($conf['database_type'] == 'mysql') { - $pdoConf[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES \'' . ($conf['charset'] ?? 'UTF8') . '\''; - } - if ($conf['persistent']) { - $pdoConf[\PDO::ATTR_PERSISTENT] = true; + if ($conf['database_type'] === 'mysql') { + $pdoConf[ + defined('Pdo\\Mysql::ATTR_INIT_COMMAND') + ? Pdo\Mysql::ATTR_INIT_COMMAND + : PDO::MYSQL_ATTR_INIT_COMMAND + ] = 'SET NAMES \'' . ($conf['charset'] ?? 'UTF8') . '\''; } /* - * A variável abaixo é setada pois caso a conexão com o banco falhe, o callback de erro será chamado e a - * variável já estará setada. + * A variável abaixo é setada pois caso a conexão com o banco falhe, o + * callback de erro será chamado e a variável já estará setada. * Caso a conexão seja feita com sucesso, a variavel é removida. */ - self::$conErrors[$database] = true; - - if (!$conf['host_name'] || !$conf['database']) { - $this->reportError('Hostname or database not defined.'); - } + self::$conErrors[$identifier] = true; $retries = $conf['retries'] ?? 3; $sleep = $conf['sleep'] ?? 1; + do { try { // a instância de conexão é estática, para nao criar uma nova a cada nova instãncia da classe - self::$conectionIds[$database] = [ - 'PDO' => new \PDO( - $conf['database_type'] . ':host=' . $conf['host_name'] . ';dbname=' . $conf['database'], - $conf['user_name'], - $conf['password'], - $pdoConf - ), - 'dbName' => $database, - ]; - unset(self::$conErrors[$database]); - } catch (\PDOException $error) { + self::$conectionIds[$identifier] = new PDO( + $conf['database_type'] . ':host=' . $conf['host_name'] . ';dbname=' . $conf['database'], + $conf['user_name'], + $conf['password'], + $pdoConf + ); + unset(self::$conErrors[$identifier]); + } catch (PDOException $error) { if ($retries) { $retries -= 1; sleep($sleep); @@ -156,68 +155,17 @@ public function connect($database) $callers = debug_backtrace(); if ( !isset($callers[1]) - || $callers[1]['class'] != 'Springy\Errors' + || $callers[1]['class'] != Errors::class || $callers[1]['function'] != 'sendReport' ) { (new Errors())->process($error); } } - } while (!isset(self::$conectionIds[$database])); + } while (!isset(self::$conectionIds[$identifier])); unset($pdoConf); - return self::$conectionIds[$database]['PDO']; - } - - /** - * Round robin database connection. - * - * @param string $database DB configuration key. - * @param array $dbconf Database configutation. - * - * @return PDO|bool - */ - private function roundRobinConnect($database, $dbconf) - { - // Lê as configurações de controle de round robin - $roundRobin = Configuration::get('db.round_robin'); - - if ($roundRobin['type'] == 'memcached') { - // Efetua controle de round robin por Memcached - $memCached = new \Memcached(); - $memCached->addServer($roundRobin['server_addr'], $roundRobin['server_port']); - - // Define o próximo servidor do pool - if (!($actual = (int) $memCached->get('dbrr_' . $database))) { - $actual = 0; - } - if (++$actual >= count($dbconf['host_name'])) { - $actual = 0; - } - - $memCached->set('dbrr_' . $database, $actual, 0); - } elseif ($roundRobin['type'] == 'file') { - // Efetua controle de round robin em arquivo - $actual = file_exists($roundRobin['server_addr'] . DS . 'dbrr_' . $database) - ? (int) file_get_contents($roundRobin['server_addr'] . DS . 'dbrr_' . $database) - : 0; - - if (++$actual >= count($dbconf['host_name'])) { - $actual = 0; - } - - file_put_contents($roundRobin['server_addr'] . DIRECTORY_SEPARATOR . 'dbrr_' . $database, $actual); - } else { - return false; - } - - // Tenta conectar ao banco e retorna o resultado - self::$conectionIds[$database] = [ - 'PDO' => $this->connect($dbconf['host_name'][$actual]), - 'dbName' => $dbconf['host_name'][$actual], - ]; - - return self::$conectionIds[$database]['PDO']; + $this->dataConnect = self::$conectionIds[$identifier]; } /** @@ -225,29 +173,18 @@ private function roundRobinConnect($database, $dbconf) * * @param string $database DB configuration key. * - * @return bool - */ - public static function connected($database = 'default') - { - return !isset(self::$conErrors[$database]) - && isset(self::$conectionIds[$database]) - && self::$conectionIds[$database]['PDO']; - } - - /** - * Closes database connection. - * - * @return void + * @deprecated 4.7.0 */ - public function disconnect() + public static function connected(string $database = 'default'): bool { - if (static::connected($this->database)) { - // a instância de conexão é estática, para não criar uma nova a cada nova instância da classe - unset(self::$conectionIds[$this->database]['PDO']); - $this->dataConnect = false; - } + trigger_error( + 'DB::connected is deprecated since version 4.7.0 and will be removed in the next release.' + . ' Use DB::isConnected instead.', + E_USER_DEPRECATED + ); - unset(self::$conectionIds[$this->database]); + return !isset(self::$conErrors[$database]) + && (self::$conectionIds[$database] ?? null) instanceof PDO; } /** @@ -257,7 +194,7 @@ public function disconnect() * * @return bool */ - public function errorReportStatus($status = null) + public function errorReportStatus(?bool $status = null): bool { if (is_bool($status)) { $this->reportError = $status; @@ -268,12 +205,8 @@ public function errorReportStatus($status = null) /** * Sends the error occurrency to the webmaster. - * - * @param string $msg - * - * @return void */ - private function reportError($msg) + private function reportError(string $msg): void { if (!$this->reportError) { return; @@ -304,66 +237,57 @@ private function reportError($msg) /** * Sets the database debug state. - * - * @param bool $debug - * - * @return void */ - public static function debug($debug) + public static function debug(bool $debug): void { self::$dbDebug = $debug; } /** * Begins a DB transaction. - * - * @param string $database DB configuration key. - * - * @return void */ - public static function beginTransaction($database = 'default') + public function beginTransaction(): void { - self::connect($database)->beginTransaction(); + $this->checkConnection(); + $this->dataConnect->beginTransaction(); } /** * Rolls back a DB transaction. - * - * @param string $database DB configuration key. - * - * @return void */ - public static function rollBack($database = 'default') + public function rollBack(): void { - self::connect($database)->rollBack(); + $this->checkConnection(); + $this->dataConnect->inTransaction() && $this->dataConnect->rollBack(); } /** * Commits a DB transaction. - * - * @param string $database DB configuration key. - * - * @return void */ - public static function commit($database = 'default') + public function commit(): void { - self::connect($database)->commit(); + $this->checkConnection(); + $this->dataConnect->inTransaction() && $this->dataConnect->commit(); } /** * Rolls back all active transactions. - * - * @return void */ - public static function rollBackAll() + public static function rollBackAll(): void { foreach (self::$conectionIds as $database) { - if ($database['PDO']->inTransaction()) { - $database['PDO']->rollBack(); - } + $database->inTransaction() && $database->rollBack(); } } + /** + * Returns current connection status. + */ + public function isConnected(): bool + { + return $this->dataConnect instanceof PDO; + } + /** * Executes a query. * @@ -385,8 +309,8 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) */ if ( (is_int($this->cacheExpires) || is_int($cacheLifeTime)) - && strtoupper(substr(ltrim($sql), 0, 19)) == 'SELECT FOUND_ROWS()' - && strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' + && mb_strtoupper(substr(ltrim($sql), 0, 19)) == 'SELECT FOUND_ROWS()' + && mb_strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' ) { $this->lastQuery = $sql . '; /* ' . md5( implode('//', array_merge([$this->lastQuery], $this->lastValues)) @@ -414,7 +338,7 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) // O comando é um SELECT e é para guardar em cache? if ( (is_int($this->cacheExpires) || is_int($cacheLifeTime)) - && strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' + && mb_strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' ) { try { $mc = new \Memcached(); @@ -431,9 +355,11 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) // Se o resultado não foi pego do cache, consulta o banco if (is_null($this->cacheStatement)) { - if (($this->resSQL = $this->dataConnect->prepare($this->lastQuery)) === false) { - $this->sqlErrorCode = $this->resSQL->errorCode(); - $this->sqlErrorInfo = $this->resSQL->errorInfo(); + $this->resSQL = $this->dataConnect->prepare($this->lastQuery); + + if ($this->resSQL === false) { + $this->sqlErrorCode = $this->dataConnect->errorCode(); + $this->sqlErrorInfo = $this->dataConnect->errorInfo(); $this->reportError('Error preparing query.'); return false; @@ -443,28 +369,19 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) $numeric = 0; foreach ($this->lastValues as $key => $where) { - switch (gettype($where)) { - case 'boolean': - $param = \PDO::PARAM_BOOL; - break; - case 'integer': - $param = \PDO::PARAM_INT; - break; - case 'NULL': - $param = \PDO::PARAM_NULL; - break; - default: - $param = \PDO::PARAM_STR; - break; - } - - if (is_numeric($key)) { - $this->resSQL->bindValue(++$numeric, $where, $param); - } else { - $this->resSQL->bindValue(':' . $key, $where, $param); - } + $param = match (gettype($where)) { + 'boolean' => PDO::PARAM_BOOL, + 'integer' => PDO::PARAM_INT, + 'NULL' => PDO::PARAM_NULL, + default => PDO::PARAM_STR, + }; + + $this->resSQL->bindValue( + is_numeric($key) ? (++$numeric) : (':' . $key), + $where, + $param + ); } - unset($key, $where, $param, $numeric); } $this->resSQL->closeCursor(); @@ -481,7 +398,7 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) // O comando é um SELECT e é para guardar em cache? if ( (is_int($this->cacheExpires) || is_int($cacheLifeTime)) - && strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' + && mb_strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' ) { try { $mc = new \Memcached(); @@ -506,7 +423,7 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) } if (self::$dbDebug || Configuration::get('system.sql_debug')) { - $conf = Configuration::get('db', self::$conectionIds[$this->database]['dbName']); + $conf = Configuration::get('db', $this->dbIdentifier); debug( '
' . $this->lastQuery . '

Values: ' . @@ -523,76 +440,62 @@ public function execute($sql, array $prepareParams = [], $cacheLifeTime = null) /** * Returns the last executed query. - * - * @return string */ - public function lastQuery() + public function lastQuery(): string { return $this->lastQuery; } /** * Returns the last error code occurred. - * - * @return string */ - public function errorCode() + public function errorCode(): ?string { return $this->dataConnect->errorCode(); } /** * Returns the last error information array. - * - * @return array */ - public function errorInfo() + public function errorInfo(): array { return $this->dataConnect->errorInfo(); } /** * Returns the string with error code occurred on last execute method call. - * - * @return string */ - public function statmentErrorCode() + public function statmentErrorCode(): ?string { return $this->sqlErrorCode; } /** * Returns the array with information about error occurred on last execute method call. - * - * @return array */ - public function statmentErrorInfo() + public function statmentErrorInfo(): array { - return $this->sqlErrorInfo; + return $this->sqlErrorInfo ?? [0, null, 'No error']; } /** * Returns the database driver name of the current connection. - * - * @return string */ - public function driverName() + public function driverName(): string { - if ($this->dataConnect === false) { - return ''; - } - - return $this->dataConnect->getAttribute(\PDO::ATTR_DRIVER_NAME); + return $this->dataConnect instanceof PDO + ? $this->dataConnect->getAttribute(PDO::ATTR_DRIVER_NAME) + : ''; } /** * Returns the DBMS version informations. - * - * @return int */ - public function serverVersion() + public function serverVersion(): mixed { - return $this->dataConnect->getAttribute(\PDO::ATTR_SERVER_VERSION); + return $this->dataConnect instanceof PDO + ? $this->dataConnect->getAttribute(PDO::ATTR_SERVER_VERSION) + : ''; } /** @@ -614,119 +517,82 @@ public function lastInsertedId($indice = null) */ public function affectedRows() { - if ($this->cacheStatement === null) { - return $this->resSQL->rowCount(); - } - - return count($this->cacheStatement); + return is_null($this->cacheStatement) + ? $this->resSQL->rowCount() + : count($this->cacheStatement); } /** * Returns all rows of the resultset. * * @param int $resultType - * - * @return array|bool */ - public function fetchAll($resultType = \PDO::FETCH_ASSOC) + public function fetchAll($resultType = PDO::FETCH_ASSOC): array { - if ($this->cacheStatement !== null) { - return $this->cacheStatement; - } elseif ($this->resSQL) { - return $this->resSQL->fetchAll($resultType); - } - - return false; + return $this->cacheStatement + ?? ($this->resSQL ? $this->resSQL->fetchAll($resultType) : []); } /** * Returns the first row of the resultset. * * @param int $resultType - * - * @return array|bool */ - public function fetchFirst($resultType = \PDO::FETCH_ASSOC) + public function fetchFirst($resultType = PDO::FETCH_ASSOC): array|bool { - if ($this->cacheStatement !== null) { - return reset($this->cacheStatement); - } elseif ($this->resSQL) { - return $this->resSQL->fetch($resultType, \PDO::FETCH_ORI_FIRST); - } - - return false; + return is_null($this->cacheStatement) + ? ($this->resSQL ? $this->resSQL->fetch($resultType) : false) + : reset($this->cacheStatement); } /** * Returns the previous row of the resultset. * * @param int $resultType - * - * @return array|bool */ - public function fetchPrev($resultType = \PDO::FETCH_ASSOC) + public function fetchPrev($resultType = PDO::FETCH_ASSOC): array|bool { - if ($this->cacheStatement !== null) { - return prev($this->cacheStatement); - } elseif ($this->resSQL) { - return $this->resSQL->fetch($resultType, \PDO::FETCH_ORI_PRIOR); - } - - return false; + return is_null($this->cacheStatement) + ? ($this->resSQL ? $this->resSQL->fetch($resultType, PDO::FETCH_ORI_PRIOR) : false) + : prev($this->cacheStatement); } /** * Returns the next row of the resultset. * * @param int $resultType - * - * @return array|bool */ - public function fetchNext($resultType = \PDO::FETCH_ASSOC) + public function fetchNext($resultType = PDO::FETCH_ASSOC): array|bool { - if ($this->cacheStatement !== null) { + if (!is_null($this->cacheStatement)) { $current = current($this->cacheStatement); next($this->cacheStatement); return $current; - } elseif ($this->resSQL) { - return $this->resSQL->fetch($resultType); } - return false; + return $this->resSQL ? $this->resSQL->fetch($resultType) : false; } /** * Returns the last row of the resultset. * * @param int $resultType - * - * @return array|bool */ - public function fetchLast($resultType = \PDO::FETCH_ASSOC) + public function fetchLast($resultType = PDO::FETCH_ASSOC): array|bool { - if ($this->cacheStatement !== null) { - return end($this->cacheStatement); - } elseif ($this->resSQL) { - return $this->resSQL->fetch($resultType, \PDO::FETCH_ORI_LAST); - } - - return false; + return is_null($this->cacheStatement) + ? ($this->resSQL ? $this->resSQL->fetch($resultType, PDO::FETCH_ORI_LAST) : false) + : end($this->cacheStatement); } /** * Returns the value of a column. - * - * @param int $var - * - * @return mixed */ - public function getColumn($var = 0) + public function getColumn(mixed $var = 0): mixed { - if ($this->cacheStatement !== null) { - $current = current($this->cacheStatement); - - return $current[$var]; + if (!is_null($this->cacheStatement)) { + return current($this->cacheStatement)[$var]; } elseif ($this->resSQL && is_numeric($var)) { return $this->resSQL->fetchColumn($var); } @@ -736,89 +602,27 @@ public function getColumn($var = 0) return false; } - /** - * Converts a brazilian date string to ISO DBMS format. - * - * @param string $datetime Date and time in brazilian format (d/m/Y H:i:s) - * @param bool $flgtime - * - * @deprecated 4.6.0 - * - * @return string - */ - public static function castDateBrToDb($datetime, $flgtime = false) - { - $date = \DateTime::createFromFormat('d/m/Y H:i:s', $datetime); - - return $flgtime ? $date->format('Y-m-d H:i:s') : $date->format('Y-m-d'); - } - - /** - * Converts an ISO DB date string to brazilian format. - * - * @param string $datetime - * @param bool $flgtime - * @param bool $sec - * - * @deprecated 4.6.0 - * - * @return string - */ - public static function castDateDbToBr($datetime, $flgtime = false, $sec = false) - { - $date = \DateTime::createFromFormat('Y-m-d H:i:s', $datetime); - - if (!$flgtime) { - return $date->format('d/m/Y'); - } - - return $sec ? $date->format('d/m/Y H:i') : $date->format('d/m/Y H:i:s'); - } - /** * Converts a date string in ISO format to UNIX timestamp. * * @param string $dateTime * + * @deprecated 4.7.0 + * * @return int */ public static function makeDbDateTime($dateTime) { + trigger_error( + 'DB::makeDbDateTime is deprecated since version 4.7.0 and will be removed in the next release.' + . ' Use DateTime::createFromFormat instead.', + E_USER_DEPRECATED + ); + if (preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2})/', $dateTime)) { return \DateTime::createFromFormat('Y-m-d H:i:s', $dateTime)->getTimestamp(); } elseif (preg_match('/^([0-9]{2})\/([0-9]{2})\/([0-9]{4})/', $dateTime)) { return \DateTime::createFromFormat('d/m/Y H:i:s', $dateTime)->getTimestamp(); } } - - /** - * Converts a date string in DBMS ISO format to a long date string in brazilian portuguese. - * - * @param string $dataTimeStamp - * - * @deprecated 4.6.0 - * - * @return string - */ - public static function longBrazilianDate($dataTimeStamp) - { - $dateTime = static::makeDbDateTime($dataTimeStamp); - $mes = [ - 'Janeiro', - 'Fevereiro', - 'Março', - 'Abril', - 'Maio', - 'Junho', - 'Julho', - 'Agosto', - 'Setembro', - 'Outubro', - 'Novembro', - 'Dezembro', - ]; - $numMes = (int) date('m', $dateTime); - - return date('d', $dateTime) . ' de ' . $mes[--$numMes] . ' de ' . date('Y', $dateTime); - } } diff --git a/springy/DB/Conditions.php b/springy/DB/Conditions.php index 94f093c..1854214 100644 --- a/springy/DB/Conditions.php +++ b/springy/DB/Conditions.php @@ -6,15 +6,10 @@ * @copyright 2016-2018 Fernando Val * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 0.6.0 */ namespace Springy\DB; -/** - * Class to construct database conditions clauses. - */ class Conditions { // Conditions array @@ -41,6 +36,7 @@ class Conditions public const OP_NOT_LIKE = 'NOT LIKE'; public const OP_MATCH = 'MATCH'; public const OP_MATCH_BOOLEAN_MODE = 'MATCH BOOLEAN'; + public const OP_BITWISE_EQUAL = '&'; // Comparison constants aliases public const OP_EQUAL_ALIAS = 'EQ'; @@ -135,6 +131,11 @@ private function condToString(array $condition, $omitCondType = false) return $expression . ' MATCH (' . $condition['column'] . ') AGAINST (?' . ($condition['operator'] === self::OP_MATCH_BOOLEAN_MODE ? ' IN BOOLEAN MODE' : '') . ')'; + case self::OP_BITWISE_EQUAL: + $this->parameters[] = $condition['value']; + $this->parameters[] = $condition['value']; + + return $expression . ' ' . $condition['column'] . ' ' . $condition['operator'] . ' ? = ?'; } throw new \Exception('Unknown condition operator.', 500); @@ -210,7 +211,7 @@ public function condition($column, $value = null, $operator = self::OP_EQUAL, $e 'type' => self::EXPR_SIMPLE, 'column' => $column, 'value' => $value, - 'operator' => strtoupper($operator), + 'operator' => mb_strtoupper($operator), 'expression' => $expression, ]; } @@ -330,7 +331,7 @@ public function filter($filter): void foreach ($filter as $field => $value) { if (is_array($value)) { foreach ($value as $method => $key) { - $method = strtolower($method); + $method = mb_strtolower($method); $this->condition($field, $key, isset($operators[$method]) ? $operators[$method] : self::OP_EQUAL); } } else { diff --git a/springy/Database/Connection.php b/springy/Database/Connection.php new file mode 100644 index 0000000..7caf12d --- /dev/null +++ b/springy/Database/Connection.php @@ -0,0 +1,573 @@ + + * @author Lucas Cardozo + * @author Allan Marques + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database; + +use Exception; +use Memcached; +use PDO; +use PDOStatement; +use Springy\Database\Connectors\Connector; +use Springy\Exceptions\SpringyException; +use Throwable; + +class Connection +{ + use LostConnectionDetector; + + /** @var array cache configuration */ + protected array $cache; + /** @var int the cache life time for next SQL statement */ + protected int $cacheLifeTime; + /** @var int the style to fetch rows statement */ + protected int $fetchStyle; + /** @var string current identity connection */ + protected string $identity; + /** @var string last query execution error */ + protected string $lastError; + /** @var string last query executed */ + protected string $lastQuery; + /** @var array last query execution prepare statements */ + protected array $lastValues; + /** @var PDOStatement|array the SQL statement */ + protected PDOStatement|array|null $statement; + + /** @var array connection instances */ + protected static array $conectionIds = []; + + public function __construct(?string $identity = null) + { + $this->cacheLifeTime = 0; + $this->fetchStyle = PDO::FETCH_ASSOC; + $this->identity = $identity ?? config_get('database.default'); + $this->lastError = ''; + $this->lastQuery = ''; + $this->lastValues = []; + $this->statement = null; + + $this->connect(); + } + + public function __destruct() + { + if ($this->statement instanceof PDOStatement) { + $this->statement->closeCursor(); + } + + $this->statement = null; + } + + /** + * Bind values to parameters. + */ + protected function bindParameters(): void + { + if (!count($this->lastValues)) { + return; + } + + $counter = 0; + + foreach ($this->lastValues as $key => $value) { + $param = match (gettype($value)) { + 'boolean' => PDO::PARAM_BOOL, + 'integer' => PDO::PARAM_INT, + 'NULL' => PDO::PARAM_NULL, + default => PDO::PARAM_STR, + }; + + $this->bindValue($key, $value, $param, $counter); + } + } + + /** + * Binds a value to a parameter. + */ + protected function bindValue(mixed $key, mixed $value, int $param, int &$counter): void + { + if (is_numeric($key)) { + $this->statement->bindValue(++$counter, $value, $param); + + return; + } + + $this->statement->bindValue(':' . $key, $value, $param); + } + + /** + * Reconnect to the database if a PDO connection is missing. + */ + protected function checkMissingConnection(): void + { + if (is_null($this->getPdo())) { + $this->connect(); + } + } + + /** + * Executes the quary again if is caused by lost connection. + * + * @throws PDOException + */ + protected function executeAgainIfLostConnection(Throwable $err): void + { + if (!$this->isLostConnection($err)) { + throw $err; + } + + try { + $this->lastError = ''; + $this->executeQuery(); + } catch (Throwable $err) { + debug($err->getMessage()); + + throw $err; + } + } + + /** + * Executes the query. + * + * @throws PDOException + */ + protected function executeQuery(): void + { + try { + $this->statement = $this->getPdo()->prepare( + $this->lastQuery, + [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY] + ); + + $this->bindParameters(); + $this->statement->closeCursor(); + $this->statement->execute(); + } catch (Throwable $err) { + $this->lastError = $err->getMessage(); + + throw $err; + } + + if ($this->cacheLifeTime) { + $this->saveCache(); + } + } + + /** + * Gets the PDO object from current connection identity. + */ + protected function getPdo(): PDO + { + if (!isset(self::$conectionIds[$this->identity])) { + $this->connect(); + } + + return self::$conectionIds[$this->identity]->getPdo(); + } + + /** + * Loads rows from cache if applicable. + */ + protected function loadCache(): void + { + // Clears the cache statement + $this->statement = null; + + if ($this->cacheLifeTime <= 0 || $this->cache['driver'] != 'memcached') { + return; + } + + $cacheKey = md5(implode('//', array_merge([$this->lastQuery], $this->lastValues))); + + try { + $mmc = new Memcached(); + $mmc->addServer($this->cache['host'], $this->cache['port']); + $sql = $mmc->get('dbCache_' . $cacheKey); + + if ($sql) { + $this->statement = $sql; + } + } catch (Exception $e) { + $this->statement = null; + } + } + + /** + * Saves the SQL statement rows in cache if applicable. + */ + protected function saveCache(): void + { + if ($this->cacheLifeTime <= 0 || $this->cache['driver'] != 'memcached') { + return; + } + + $cacheKey = md5(implode('//', array_merge([$this->lastQuery], $this->lastValues))); + + try { + $mmc = new Memcached(); + $mmc->addServer($this->cache['host'], $this->cache['port']); + + $rows = $this->getAll(); + + $mmc->set('dbCache_' . $cacheKey, $rows, $this->cacheLifeTime); + + $this->statement->closeCursor(); + $this->statement = $rows; + } catch (Throwable $err) { + debug($this->lastQuery); + debug('Erro: ' . $err->getMessage()); + } + } + + /** + * Saves the query string in lastQuery property. + */ + protected function setLastQuery(string $query): void + { + if ( + $this->cacheLifeTime > 0 + && mb_strtoupper(substr(ltrim($query), 0, 19)) == 'SELECT FOUND_ROWS()' + && mb_strtoupper(substr(ltrim($this->lastQuery), 0, 7)) == 'SELECT ' + ) { + $this->lastQuery = $query . '; /* ' . md5( + implode( + '//', + array_merge([$this->lastQuery], $this->lastValues) + ) + ) . ' */'; + + return; + } + + $this->lastQuery = $query; + } + + /** + * Connects to the DBMS. + * + * @throws SpringyException + */ + public function connect(): void + { + // Is a connector to the identity? + if ( + isset(self::$conectionIds[$this->identity]) + && self::$conectionIds[$this->identity]->getPdo() !== null + ) { + return; + } + + $driver = config_get('database.connections.' . $this->identity . '.driver'); + + if (!class_exists($driver)) { + throw new SpringyException('Database driver not found.'); + } + + /** @var Connector */ + $connector = new $driver(config_get('database.connections.' . $this->identity)); + + if (!($connector instanceof Connector)) { + throw new SpringyException('Database driver not supported.'); + } + + $this->cache = config_get( + 'database.cache', + [ + 'driver' => 'none', + ] + ); + + self::$conectionIds[$this->identity] = $connector; + $connector->connect(); + } + + /** + * Closes database connection. + */ + public function disconnect(): void + { + if (!isset(self::$conectionIds[$this->identity])) { + return; + } + + unset(self::$conectionIds[$this->identity]); + } + + /** + * Encloses the keyword by enclosure char to escapes it. + */ + public function enclose(string $keyword): string + { + return $this->getConnector()->enclose($keyword); + } + + /** + * Returns true if connection was stablished. + */ + public function isConnected(): bool + { + $connection = self::$conectionIds[$this->identity] ?? null; + + return ($connection !== null) && ($connection->getPdo() instanceof PDO); + } + + /** + * Begins a DB transaction. + */ + public function beginTransaction(): void + { + $this->getPdo()->beginTransaction(); + } + + /** + * Commits a DB transaction. + */ + public function commit(): void + { + $this->getPdo()->commit(); + } + + /** + * Executes a query and returns the quantity of rows affected. + */ + public function execute(string $query, array $params = []): int + { + $this->run($query, $params); + + return $this->affectedRows(); + } + + /** + * Rolls back a DB transaction. + */ + public function rollBack(): void + { + $this->getPdo()->rollBack(); + } + + /** + * Executes a query. + */ + public function run(string $query, array $params = []): void + { + $this->lastError = ''; + $this->statement = null; + + $this->setLastQuery($query); + + $this->lastValues = $params; + + $this->loadCache(); + + if ($this->statement !== null) { + return; + } + + $this->checkMissingConnection(); + + try { + $this->executeQuery(); + } catch (Throwable $err) { + $this->executeAgainIfLostConnection($err); + } + } + + /** + * Runs a select query and returns the array of found rows. + */ + public function select( + string $query, + array $params = [], + ?int $fetchStyle = null, + int $cacheLifeTime = 0 + ): array { + $this->cacheLifeTime = $cacheLifeTime; + $this->run($query, $params); + $this->fetchStyle = $fetchStyle ?? $this->fetchStyle; + $this->statement = $this->getAll(); + $this->cacheLifeTime = 0; + + return $this->statement ?? []; + } + + /** + * Returns the number of rows affected by the last SQL statement. + */ + public function affectedRows(): int + { + if ($this->statement instanceof PDOStatement) { + return $this->statement->rowCount(); + } elseif (is_array($this->statement)) { + return count($this->statement); + } + + return 0; + } + + /** + * Returns the current row of the statement and moves the cursor to the next row. + * + * @return array|bool + */ + public function fetch(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + $current = current($this->statement); + next($this->statement); + + return $current; + } + + /** + * Returns all rows of the resultset. + */ + public function getAll(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $rows = $this->statement->fetchAll($this->fetchStyle); + $this->statement->closeCursor(); + $this->statement = $rows; + } + + return $this->statement; + } + + /** + * Returns the value of a column. + */ + public function getColumn(string $var): mixed + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + $current = current($this->statement); + + return $current[$var] ?? null; + } + + /** + * Gets the connector object. + */ + public function getConnector(): Connector + { + if (!isset(self::$conectionIds[$this->identity])) { + $this->connect(); + } + + return self::$conectionIds[$this->identity]; + } + + /** + * Returns the current row of the resultset and moves the cursor to next record. + */ + public function getCurrent(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + return current($this->statement); + } + + /** + * Returns the database driver name of the current connection. + */ + public function getDriverName(): ?string + { + return $this->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); + } + + /** + * Returns the last error occurred. + */ + public function getError(): string + { + return $this->lastError; + } + + /** + * Resets the cursor to the first row of the statement and returns it. + */ + public function getFirst(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + return reset($this->statement); + } + + /** + * Moves the cursor to the last row of the statement and returns it. + */ + public function getLast(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + return end($this->statement); + } + + /** + * Returns the value of the auto increment columns in last INSERT. + */ + public function getLastInsertedId(?string $name = null): int + { + return $this->getPdo()->lastInsertId($name); + } + + /** + * Returns the last executed query. + */ + public function getLastQuery(): string + { + return $this->lastQuery; + } + + /** + * Returns the next row of the statement. + * + * Be careful when using this method because it moves the cursos before fetch the record. + */ + public function getNext(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + return next($this->statement); + } + + /** + * Moves the cursor the previous row of the statement and returns it. + */ + public function getPrev(): array|bool + { + if ($this->statement instanceof PDOStatement) { + $this->getAll(); + } + + return prev($this->statement); + } + + /** + * Returns the DBMS version informations. + */ + public function getServerVersion(): mixed + { + return $this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + } +} diff --git a/springy/Database/Connectors/Connector.php b/springy/Database/Connectors/Connector.php new file mode 100644 index 0000000..e6385df --- /dev/null +++ b/springy/Database/Connectors/Connector.php @@ -0,0 +1,184 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database\Connectors; + +use Closure; +use Exception; +use PDO; +use Springy\Exceptions\SpringyException; + +/** + * DBMS connector basic implementation. + */ +class Connector +{ + protected string $database; // name of the database + protected int $retries; // connection tentative possible + protected int $retrySleep; // sleep time in seconds between each try connection + protected $encloseCharOpn = '`'; + protected $encloseCharCls = '`'; + protected Closure|null $onSuccessConnect; // callback function to execute when connection is successful + /** @var array PDO constructor options */ + protected array $options = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + protected string $password; // the database user password + protected PDO|null $pdo; + protected string $timezone; // timezone configuration + protected string $username; // the database username + + public function __construct(array $settings) + { + $this->pdo = null; + $this->onSuccessConnect = null; + $this->retries = 0; + $this->retrySleep = 0; + $this->timezone = $settings['timezone'] ?? ''; + $this->setDatabase($settings['database'] ?? ''); + $this->setUsername($settings['username'] ?? ''); + $this->setPassword($settings['password'] ?? ''); + } + + /** + * Connects with the database. + */ + public function connect(): PDO + { + do { + try { + $this->pdo = new PDO($this->getDsn(), $this->username, $this->password, $this->options); + } catch (Exception $exception) { + if ($this->retries) { + $this->retries -= 1; + sleep($this->retrySleep); + + continue; + } + + throw $exception; + } + } while (is_null($this->pdo)); + + if (is_callable($this->onSuccessConnect)) { + call_user_func($this->onSuccessConnect); + } + + return $this->pdo; + } + + /** + * Encloses the keyword by enclosure char to escapes it. + */ + public function enclose(string $keyword): string + { + if ($keyword === '*' || substr($keyword, 0, 1) == $this->encloseCharOpn) { + return $keyword; + } + + return $this->encloseCharOpn . $keyword . $this->encloseCharCls; + } + + /** + * Converts SELECT to COUNT rows format. + */ + public function foundRowsSelect(string $select): string + { + $reg = '/(SELECT )(.*)( FROM .*)( ORDER BY .+)?( GROUP BY .+( HAVING .*)?)( LIMIT [\d]+)( OFFSET [\d]+.*)?/mi'; + $subst = '$1COUNT(0) AS found_rows$3;'; + + return preg_replace($reg, $subst, $select); + } + + /** + * Gets the database name. + */ + public function getDatabase(): string + { + return $this->database; + } + + /** + * Gets the DSN string. + * + * Must be implemented into child connector class. + */ + public function getDsn(): string + { + return ''; + } + + /** + * Returns the database user password. + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * Returns the PDO object. + */ + public function getPdo(): PDO + { + return $this->pdo; + } + + /** + * Returns the database username. + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * Converts SELECT command to its optimized form when limiting rows. + */ + public function paginatedSelect(string $select): string + { + $reg = '/^(SELECT )(.*)$/mi'; + $subst = '$1$2'; + + return preg_replace($reg, $subst, $select); + } + + /** + * Sets the database name. + */ + public function setDatabase(string $name): void + { + if (!$name) { + throw new SpringyException('Database name undefined.'); + } + + $this->database = $name; + } + + /** + * Sets the password to connect to the database. + */ + public function setPassword(string $password) + { + $this->password = $password; + } + + /** + * Sets the username to connecto to. + */ + public function setUsername(string $name): void + { + $this->username = $name; + } +} diff --git a/springy/Database/Connectors/ConnectorInterface.php b/springy/Database/Connectors/ConnectorInterface.php new file mode 100644 index 0000000..8edadaa --- /dev/null +++ b/springy/Database/Connectors/ConnectorInterface.php @@ -0,0 +1,28 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database\Connectors; + +interface ConnectorInterface +{ + /** + * Returns the name of function to get current date and time from DBMS. + * + * @return string + */ + public function getCurrDate(): string; + + /** + * Gets the DSN string. + * + * @return string + */ + public function getDsn(): string; +} diff --git a/springy/Database/Connectors/MySQL.php b/springy/Database/Connectors/MySQL.php new file mode 100644 index 0000000..c934460 --- /dev/null +++ b/springy/Database/Connectors/MySQL.php @@ -0,0 +1,91 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database\Connectors; + +use PDO; +use Springy\Exceptions\SpringyException; + +class MySQL extends Connector implements ConnectorInterface +{ + protected string $charset; + protected string $host; + protected int $port; + protected string $socket; + + public function __construct(array $settings) + { + parent::__construct($settings); + + $this->charset = $settings['charset'] ?? 'utf8mb4'; + $this->host = $settings['host'] ?? ''; + $this->onSuccessConnect = $this->afterConnectSettings(...); + $this->port = $settings['port'] ?? 3306; + $this->socket = $settings['socket'] ?? ''; + $this->retries = $settings['retries'] ?? 3; + $this->retrySleep = $settings['retry_sleep'] ?? 1; + + if (!$this->host && !$this->socket) { + throw new SpringyException('Undefined database server host and socket.'); + } + + $this->options[ + defined('Pdo\\Mysql::ATTR_INIT_COMMAND') + ? Pdo\Mysql::ATTR_INIT_COMMAND + : PDO::MYSQL_ATTR_INIT_COMMAND + ] = 'SET NAMES \'' . $this->charset . '\''; + $this->options[PDO::ATTR_PERSISTENT] = $config['persistent'] ?? true; + } + + /** + * Configures database connection after the connection stablished. + * + * @return void + */ + protected function afterConnectSettings(): void + { + if ($this->timezone) { + $this->pdo->prepare('SET time_zone="' . $this->timezone . '"')->execute(); + } + } + + /** + * Gets string for HOST or SOCKET connector DSN. + * + * @return string + */ + protected function getHostOrSocket(): string + { + return $this->socket + ? 'unix_socket=' . $this->socket + : 'host=' . $this->host . ';port=' . $this->port; + } + + public function foundRowsSelect(string $select): string + { + $reg = '/^(SELECT )(.*)$/mi'; + $subst = '$1FOUND_ROWS() AS found_rows;'; + + return preg_replace($reg, $subst, $select); + } + + /** + * Returns the name of function to get current date and time from DBMS. + */ + public function getCurrDate(): string + { + return 'NOW()'; + } + + public function getDsn(): string + { + return 'mysql:' . $this->getHostOrSocket() . ';dbname=' . $this->database; + } +} diff --git a/springy/Database/Connectors/PostgreSQL.php b/springy/Database/Connectors/PostgreSQL.php new file mode 100644 index 0000000..a8132cf --- /dev/null +++ b/springy/Database/Connectors/PostgreSQL.php @@ -0,0 +1,103 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database\Connectors; + +use PDO; +use Springy\Exceptions\SpringyException; + +class PostgreSQL extends Connector implements ConnectorInterface +{ + protected $encloseCharOpn = '"'; + protected $encloseCharCls = '"'; + + protected string $charset; + protected string $host; + protected int $port; + protected string $schema; + protected string $ssl; // the SSL options + + public function __construct(array $config) + { + parent::__construct($config); + + $this->charset = $config['charset'] ?? 'UTF8'; + $this->host = $settings['host'] ?? ''; + $this->onSuccessConnect = $this->afterConnectSettings(...); + $this->port = $config['port'] ?? 5432; + $this->retries = $config['retries'] ?? 3; + $this->retrySleep = $config['retry_sleep'] ?? 1; + $this->schema = $config['schema'] ?? ''; + + if (!$this->host) { + throw new SpringyException('Undefined database server host.'); + } + + unset($this->options[PDO::ATTR_EMULATE_PREPARES]); + + $this->ssl = ''; + + foreach (['sslmode', 'sslcert', 'sslkey', 'sslrootcert'] as $option) { + if (isset($config[$option])) { + $this->ssl .= ';' . $option . '=' . $config[$option]; + } + } + } + + /** + * Configures database connection after the connection stablished. + */ + protected function afterConnectSettings(): void + { + if ($this->charset) { + $this->pdo->prepare('SET NAMES \'' . $this->charset . '\'')->execute(); + } + + if ($this->timezone) { + $this->pdo->prepare('SET TIME ZONE \'' . $this->timezone . '\'')->execute(); + } + + if ($this->schema) { + $this->pdo->prepare('SET SCHEMA \'' . $this->schema . '\'')->execute(); + } + } + + public function foundRowsSelect(string $select): string + { + $reg = '/^(SELECT )(.*)$/mi'; + $subst = ''; + + return preg_replace($reg, $subst, $select); + } + + /** + * Returns the name of function to get current date and time from DBMS. + */ + public function getCurrDate(): string + { + return 'NOW()'; + } + + public function getDsn(): string + { + return 'pgsql:' . ($this->host ? 'host=' . $this->host : '') + . ';port=' . $this->port + . ';dbname=' . $this->database + . $this->ssl; + } + + public function paginatedSelect(string $select): string + { + $reg = '/^(SELECT )(.+)( FROM (.*)( LIMIT [\d]+)( OFFSET [\d]+)?.*){1}$/mi'; + $subst = '$1$2, COUNT(*) OVER() AS found_rows$3'; + + return preg_replace($reg, $subst, $select); + } +} diff --git a/springy/Database/Connectors/SQLite.php b/springy/Database/Connectors/SQLite.php new file mode 100644 index 0000000..f0a0165 --- /dev/null +++ b/springy/Database/Connectors/SQLite.php @@ -0,0 +1,45 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Database\Connectors; + +use Springy\Exceptions\SpringyException; + +class SQLite extends Connector implements ConnectorInterface +{ + protected $encloseCharOpn = '"'; + protected $encloseCharCls = '"'; + + /** + * Returns the name of function to get current date and time from DBMS. + */ + public function getCurrDate(): string + { + return 'datetime(\'now\')'; + } + + public function getDsn(): string + { + return 'sqlite:' . $this->database; + } + + public function setDatabase(string $database): void + { + parent::setDatabase($database); + + if ($database !== ':memory:') { + $path = realpath($database); + + if ($path === false) { + throw new SpringyException('Database "' . $database . '" does not exists.'); + } + } + } +} diff --git a/springy/Database/LostConnectionDetector.php b/springy/Database/LostConnectionDetector.php new file mode 100644 index 0000000..ad7cd59 --- /dev/null +++ b/springy/Database/LostConnectionDetector.php @@ -0,0 +1,27 @@ +getMessage(), $messages); + } +} diff --git a/springy/Database/LostConnectionMessages.txt b/springy/Database/LostConnectionMessages.txt new file mode 100644 index 0000000..86f0b8f --- /dev/null +++ b/springy/Database/LostConnectionMessages.txt @@ -0,0 +1,64 @@ +server has gone away +Server has gone away +no connection to the server +Lost connection +is dead or not enabled +Error while sending +decryption failed or bad record mac +server closed the connection unexpectedly +SSL connection has been closed unexpectedly +Error writing data to the connection +Resource deadlock avoided +Transaction() on null +child connection forced to terminate due to client_idle_limit +query_wait_timeout +reset by peer +Physical connection is not usable +TCP Provider: Error code 0x68 +ORA-03114 +Packets out of order. Expected +Adaptive Server connection failed +Communication link failure +connection is no longer usable +Login timeout expired +SQLSTATE[HY000] [2002] Connection refused +running with the --read-only option so it cannot execute this statement +The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made trestore the connection.', +SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again +SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known +SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for +SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected +SQLSTATE[HY000] [2002] Connection timed out +SSL: Connection timed out +SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry. +Temporary failure in name resolution +SQLSTATE[08S01]: Communication link failure +SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host +SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host +The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior. +SQLSTATE[08006] [7] could not translate host name +TCP Provider: Error code 0x274C +SQLSTATE[HY000] [2002] No such file or directory +SSL: Operation timed out +Reason: Server is in script upgrade mode. Only administrator can connect at this time. +Unknown $curl_error_code: 77 +SSL: Handshake timed out +SSL error: sslv3 alert unexpected message +unrecognized SSL error code: +SQLSTATE[HY000] [1045] Access denied for user +SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it +SQLSTATE[HY000] [2002] A connection attempt failed because the connected party did not properly respond after a period of time, or establisheconnection failed because connected host has failed to respond', +SQLSTATE[HY000] [2002] Network is unreachable +SQLSTATE[HY000] [2002] The requested address is not valid in its context +SQLSTATE[HY000] [2002] A socket operation was attempted to an unreachable network +SQLSTATE[HY000] [2002] Operation now in progress +SQLSTATE[HY000] [2002] Operation in progress +SQLSTATE[HY000]: General error: 3989 +went away +No such file or directory +server is shutting down +failed to connect to +Channel connection is closed +Connection lost +Broken pipe +SQLSTATE[25006]: Read only sql transaction: 7 \ No newline at end of file diff --git a/springy/Errors.php b/springy/Errors.php index aa4c141..763d704 100644 --- a/springy/Errors.php +++ b/springy/Errors.php @@ -9,8 +9,6 @@ * @author Fernando Val * @author Lucas Cardozo * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 3.2.2 */ namespace Springy; @@ -18,6 +16,7 @@ use Exception; use PDOException; use Springy\Exceptions\SpringyException; +use Springy\Mail\Mailer; use Springy\Utils\Strings; use Throwable; @@ -149,7 +148,7 @@ protected function getErrorName(Throwable $error): string E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', - E_STRICT => 'Fatal Error', + 2048 => 'Fatal Error', // E_STRICT - Deprecated in PHP 8.4.0 1044 => 'Access Denied to Database', E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'Deprecated by User', @@ -281,7 +280,7 @@ protected function isRecoverableWarning(Throwable $error): bool * * @return void */ - public function process(Throwable $error, $httpCode = 500) + public function process(Throwable $error, $httpCode = 500): void { if ( in_array($error->getCode(), Kernel::getIgnoredError()) @@ -340,7 +339,7 @@ protected function saveInDb(string $errorId, Throwable $error): bool $dbc = new DB($dbsrv); - if (!DB::connected($dbsrv)) { + if (!$dbc->isConnected()) { return true; } @@ -376,6 +375,10 @@ protected function saveInDb(string $errorId, Throwable $error): bool */ protected function sendEmail(string $errorId, $errorType, Throwable $error): void { + if (!config_get('mail.errors_go_to')) { + return; + } + $html = file_get_contents($this->getTplPath('system-error-email.html')); $html = str_replace('{systemName}', app_name(), $html); $html = str_replace('{sistemVersion}', app_version(), $html); @@ -386,20 +389,17 @@ protected function sendEmail(string $errorId, $errorType, Throwable $error): voi $html = str_replace('{errorFile}', $error->getFile(), $html); $html = str_replace('{errorLine}', $error->getLine(), $html); - $email = new Mail(); - $email->to(config_get('mail.errors_go_to'), 'System Admin'); - $email->from( + (new Mailer( config_get('mail.system_adm_mail'), - app_name() . ' - System Error Report' - ); - $email->subject( + app_name() . ' - System Error Report', 'Error on ' . app_name() . ' v' . app_version() . ' [' . Kernel::environment() . '] at ' . URI::getHost() - ); - $email->body($html); - $email->send(); + )) + ->addTo(config_get('mail.errors_go_to'), 'System Admin') + ->setBody($html, true) + ->send(); } /** @@ -410,9 +410,9 @@ protected function sendEmail(string $errorId, $errorType, Throwable $error): voi * @param int $httpCode * @param Throwable $error * - * @return void + * @return never */ - public function sendReport(string $errorId, string $title, int $httpCode, Throwable $error) + public function sendReport(string $errorId, string $title, int $httpCode, Throwable $error): never { restore_error_handler(); @@ -436,90 +436,27 @@ public function sendReport(string $errorId, string $title, int $httpCode, Throwa } /** - * Deletes an error from error log table. + * @deprecated 4.7 + * @see ErrorsList::delete() + * @throws SpringyException * - * @param string $errorId - * - * @return void + * @return never */ - public function bugSolved(string $errorId): void + public function bugSolved(string $errorId) { - $dbsrv = config_get('system.system_error.db_server') ?: 'default'; - $table = config_get('system.system_error.table_name') ?: 'system_errors'; - $dbcon = new DB($dbsrv); - - if (!DB::connected($dbsrv)) { - throw_error(500, 'Fail to connect to database'); - } - - if ($errorId == 'all') { - $dbcon->execute('DELETE FROM ' . $table, []); - echo 'ALL errors deleted from error log.'; - - return; - } - - $idList = explode(',', $errorId); - $dbcon->execute( - 'DELETE FROM ' . $table - . ' WHERE error_code ' . ( - count($idList) > 1 - ? 'in (' . implode(',', array_fill(0, count($idList), '?')) . ')' - : '= ?' - ), - count($idList) > 1 ? $idList : [$errorId] - ); - echo 'Error(s) ID ' . $errorId . ' deleted from log.'; + throw new SpringyException('Deprecated method - user ErrorsList::delete()', E_USER_DEPRECATED); } /** - * Prints the error log content. + * @deprecated 4.7 + * @see ErrorsList::print() + * @throws SpringyException * - * @return void + * @return never */ public function bugList() { - $template = $this->getTplPath('errors-list.html'); - - if (!is_file($template)) { - throw_error(404, 'Not Found'); - } - - $dbsrv = config_get('system.system_error.db_server') ?: 'default'; - $table = config_get('system.system_error.table_name') ?: 'system_errors'; - $dbcon = new DB($dbsrv); - - if (!DB::connected($dbsrv)) { - throw_error(500, 'Fail to connect to database'); - } - - $order_column = URI::getParam('orderBy') ?: 'last_time'; - $order_type = URI::getParam('sort') ?: 'DESC'; - $dbcon->execute( - 'SELECT id, error_code, description, occurrences, last_time, details' . - ' FROM ' . $table . - ' ORDER BY ' . $order_column . ' ' . $order_type - ); - $errList = array_map( - function ($row) { - $json = json_decode($row['details']); - - if (!json_last_error()) { - $row['details'] = $json; - } - - return $row; - }, - $dbcon->fetchAll() - ); - - $output = file_get_contents($template); - $output = str_replace('{systemName}', app_name(), $output); - $output = str_replace('{sistemVersion}', app_version(), $output); - $output = str_replace('{errorsList}', json_encode($errList), $output); - - header('Content-type: text/html; charset=UTF-8', true, 200); - echo $output; + throw new SpringyException('Deprecated method - user ErrorsList::print()', E_USER_DEPRECATED); } /** @@ -531,7 +468,7 @@ function ($row) { * * @return void */ - private function printHtml(string $errorId, $errorType, Throwable $error) + private function printHtml(string $errorId, $errorType, Throwable $error): void { $lineFeed = "\n"; @@ -571,9 +508,10 @@ private function printHtml(string $errorId, $errorType, Throwable $error) $output = file_get_contents($this->getTplPath('system-error.html')); $output = str_replace('{systemName}', app_name(), $output); - $output = str_replace('{sistemVersion}', app_version(), $output); + $output = str_replace('{systemVersion}', app_version(), $output); $output = str_replace('{errorId}', $errorId, $output); $output = str_replace('{errorCode}', $errorType, $output); + $output = str_replace('{errorMessage}', $error->getMessage(), $output); echo $output; } diff --git a/springy/Events/Mediator.php b/springy/Events/Mediator.php index f30f078..a40038b 100644 --- a/springy/Events/Mediator.php +++ b/springy/Events/Mediator.php @@ -6,8 +6,6 @@ * @copyright 2015 Fernando Val * @author Fernando Val * @author Allan Marques - * - * @version 0.3.1 */ namespace Springy\Events; @@ -15,22 +13,18 @@ use Closure; use Springy\Container\DIContainer; -/** - * Mediator classe. - */ class Mediator { - /** @var DIContainer */ - protected $container; + protected DIContainer $container; /** @var array registered handlers */ - protected $handlers; + protected array $handlers; /** @var array handlers masters (wildcards) */ - protected $masterHandlers; + protected array $masterHandlers; /** @var array handlers ordered by priority */ - protected $orderedHandlers; + protected array $orderedHandlers; /** @var string|null event name been fired */ - protected $currentEvent; + protected string|null $currentEvent; /** * Constructor. @@ -43,14 +37,11 @@ public function __construct(?DIContainer $container = null) $this->handlers = []; $this->masterHandlers = []; $this->orderedHandlers = []; + $this->currentEvent = null; } /** - * Sets container. - * - * @param DIContainer $container - * - * @return void + * Sets the container. */ public function setContainer(DIContainer $container): void { @@ -59,8 +50,6 @@ public function setContainer(DIContainer $container): void /** * Gets the container. - * - * @return DIContainer */ public function getContainer(): DIContainer { @@ -73,40 +62,28 @@ public function getContainer(): DIContainer * @param array|string $events nome ou conjunto de nomes que representam o evento. * @param Closure|string $handler objeto, closure ou nome de depenência que irá tratar o evento. * @param int $priority prioridade do handler na pilha de execução, maior, mais importante. - * - * @return void */ - public function registerHandlerFor($events, $handler, int $priority = 0): void + public function registerHandlerFor(array|string $events, Closure|string $handler, int $priority = 0): void { - foreach ((array) $events as $event) { //Para cada nom de evento - if (strpos($event, '.*') !== false) {//Se houver '*' é um masterHandler + // Para cada nom de evento + foreach ((array) $events as $event) { + // Se houver '*' é um masterHandler + if (strpos($event, '.*') !== false) { $this->registerMasterHandler($event, $handler); - continue; //Registra-o e reoturn + + continue; // Registra-o e return } - //Registra o handler de acordo com sua prioridade + // Registra o handler de acordo com sua prioridade $this->handlers[$event][$priority][] = $this->resolveHandler($handler); - unset($this->orderedHandlers[$event]); //Reseta a ordem de prioridade dos handlers + // Reseta a ordem de prioridade dos handlers + unset($this->orderedHandlers[$event]); } } - /** - * Alias of registerHandlerFor(). - * - * @deprecated 4.5.0 - */ - public function on($event, $handler, $priority = 0) - { - $this->registerHandlerFor($event, $handler, $priority); - } - /** * Checks whether event identified by $evet exists. - * - * @param string $event - * - * @return bool */ public function hasHandlersFor(string $event): bool { @@ -115,10 +92,6 @@ public function hasHandlersFor(string $event): bool /** * Removes all handlers for the event identified by $event. - * - * @param string $event - * - * @return void */ public function forget(string $event): void { @@ -126,16 +99,6 @@ public function forget(string $event): void unset($this->orderedHandlers[$event]); } - /** - * Alias of forget(). - * - * @deprecated 4.5.0 - */ - public function off($event) - { - $this->forget($event); - } - /** * Fires the event identified by $event. * @@ -171,8 +134,6 @@ public function fire(string $event, array $data = []): array /** * Returns the event been fired. - * - * @return string|null */ public function current(): ?string { @@ -198,37 +159,29 @@ public function subscribe($handler): void /** * Resolve o tipo do handler. * - * @param mixed $handler Handler para ser resolvido + * @param Closure|string $handler Handler para ser resolvido * - * @return mixed O handler resolvido. + * @return Closure O handler resolvido. */ - protected function resolveHandler($handler) + protected function resolveHandler(Closure|string $handler): Closure { - if (is_string($handler)) { //Se string, nome de dependencia - return $this->createHandler($handler); //resolver dependencia - } - - return $handler; + return is_string($handler) ? $this->createHandler($handler) : $handler; } /** * Cria um handler de acordo com o nome do objeto e ação passado por parâmetro, * resolvido pelo container de aplicação (Ex. cache@store). * - * @param mixed $handler Handler para ser criado - * - * @return Closure. + * @param string $handler Handler para ser criado */ - protected function createHandler($handler): Closure + protected function createHandler(string $handler): Closure { $container = $this->container; return function () use ($handler, $container) { $parts = explode('@', $handler); - - $method = count($parts) == 2 ? $parts[1] : 'handle'; //Se não houver ação, o padrão é 'handle' - - $service = [$container[$parts[0]], $method]; //Cria o callable como handler do evento + $method = count($parts) == 2 ? $parts[1] : 'handle'; // Se não houver ação, o padrão é 'handle' + $service = [$container[$parts[0]], $method]; // Cria o callable como handler do evento return call_user_func_array($service, func_get_args()); }; @@ -242,19 +195,15 @@ protected function createHandler($handler): Closure * @param string $event Nome do evento * @param mixed $handler Master Handler. */ - protected function registerMasterHandler($event, $handler) + protected function registerMasterHandler(string $event, mixed $handler): void { $this->masterHandlers[$this->getMasterHandlerKey($event)][] = $this->resolveHandler($handler); } /** * Extrai o nome do evento no qual o master handler irá ficar 'escutando'. - * - * @param string $event Nome do evento - * - * @return string */ - protected function getMasterHandlerKey($event) + protected function getMasterHandlerKey(string $event): string { $parts = explode('*', $event); @@ -263,12 +212,8 @@ protected function getMasterHandlerKey($event) /** * Retorna os handlers para o evento requisitado. - * - * @param string $event Nome do evento - * - * @return array */ - protected function getHandlersFor($event) + protected function getHandlersFor(string $event): array { if (!isset($this->orderedHandlers[$event])) { $this->orderHandlersFor($event); @@ -282,17 +227,13 @@ protected function getHandlersFor($event) /** * Retorna os masters handlers para o evento indicado. - * - * @param string $event Nome do evento - * - * @return array */ - protected function getMasterHandlersFor($event) + protected function getMasterHandlersFor(string $event): array { $masterHandlers = []; foreach ($this->masterHandlers as $masterKey => $handlers) { - //Se nome do master handler estiver contido no nome do do evento + // Se nome do master handler estiver contido no nome do do evento if (strpos($event, $masterKey) === 0) { $masterHandlers = array_merge($masterHandlers, $handlers); } @@ -303,10 +244,8 @@ protected function getMasterHandlersFor($event) /** * Ordena os handlers de acordo com suas prioridades. - * - * @param string $event Nome do evento. */ - protected function orderHandlersFor($event) + protected function orderHandlersFor(string $event): void { $sorted = $this->handlers[$event]; @@ -317,12 +256,8 @@ protected function orderHandlersFor($event) /** * Cria e retorna uma nova instancia desta classe. - * - * @param \Springy\Container\DIContainer $container - * - * @return static */ - public static function newInstance(DIContainer $container = null) + public static function newInstance(?DIContainer $container = null): self { return new static($container); } diff --git a/springy/Exceptions/HttpError.php b/springy/Exceptions/HttpError.php new file mode 100644 index 0000000..bcc77fa --- /dev/null +++ b/springy/Exceptions/HttpError.php @@ -0,0 +1,29 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Exceptions; + +use Error; +use Throwable; + +class HttpError extends Error +{ + public function __construct( + string $message = 'Internal Server Error', + int $code = 500, + ?Throwable $previous = null + ) { + $dbt = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1); + $this->file = $dbt[0]['file'] ?? ''; + $this->line = $dbt[0]['line'] ?? 0; + + parent::__construct($message, $code, $previous); + } +} diff --git a/springy/Exceptions/HttpErrorForbidden.php b/springy/Exceptions/HttpErrorForbidden.php new file mode 100644 index 0000000..b7a3abf --- /dev/null +++ b/springy/Exceptions/HttpErrorForbidden.php @@ -0,0 +1,24 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Exceptions; + +use Throwable; + +class HttpErrorForbidden extends HttpError +{ + public function __construct( + string $message = 'Forbidden', + int $code = 403, + ?Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + } +} diff --git a/springy/Exceptions/HttpErrorNotFound.php b/springy/Exceptions/HttpErrorNotFound.php new file mode 100644 index 0000000..26cd2d0 --- /dev/null +++ b/springy/Exceptions/HttpErrorNotFound.php @@ -0,0 +1,24 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Exceptions; + +use Throwable; + +class HttpErrorNotFound extends HttpError +{ + public function __construct( + string $message = 'Not Found', + int $code = 404, + ?Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + } +} diff --git a/springy/Exceptions/SpringyException.php b/springy/Exceptions/SpringyException.php index 3924706..637bef6 100644 --- a/springy/Exceptions/SpringyException.php +++ b/springy/Exceptions/SpringyException.php @@ -6,8 +6,6 @@ * @copyright 2023 Fernando Val * @author Fernando Val * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT - * - * @version 1.0.0 */ namespace Springy\Exceptions; @@ -15,34 +13,19 @@ use RuntimeException; use Throwable; -/** - * Springy Exception class. - */ class SpringyException extends RuntimeException { - /** @var array|null error context */ - protected $context; - - /** - * Constructor. - * - * @param int $code - * @param string $message - * @param string $file - * @param int $line - * @param array|null $context - */ public function __construct( - string $message = null, + string $message, int $code = E_USER_ERROR, - Throwable $previous = null, - string $file = null, - int $line = null + ?Throwable $previous = null, + ?string $file = null, + ?int $line = null ) { if (is_null($file) || is_null($line)) { $dbt = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 1); - $file = $dbt[0]['file']; - $line = $dbt[0]['line']; + $file = $dbt[0]['file'] ?? ''; + $line = $dbt[0]['line'] ?? 0; } $this->file = $file; diff --git a/springy/Files/UploadedFile.php b/springy/Files/UploadedFile.php index b9e65b3..0a31726 100644 --- a/springy/Files/UploadedFile.php +++ b/springy/Files/UploadedFile.php @@ -6,8 +6,6 @@ * @copyright 2014 Fernando Val * @author Allan Marques * @author Fernando Val - * - * @version 0.1.5 */ namespace Springy\Files; @@ -190,7 +188,7 @@ public function moveTo($directory, $name = null) */ public static function getMaxFilesize() { - $iniMax = strtolower(ini_get('upload_max_filesize')); + $iniMax = mb_strtolower(ini_get('upload_max_filesize')); if ('' === $iniMax) { return PHP_INT_MAX; diff --git a/springy/Kernel.php b/springy/Kernel.php index 879a7ec..9ab5402 100644 --- a/springy/Kernel.php +++ b/springy/Kernel.php @@ -12,6 +12,8 @@ namespace Springy; use Dotenv\Dotenv; +use Springy\Core\ErrorsList; +use Springy\Exceptions\HttpErrorNotFound; /** * Framework kernel class. @@ -21,7 +23,7 @@ class Kernel { // Framework version - public const VERSION = '4.6.2 (this constant is deprecated)'; + public const VERSION = '4.7.0'; // Default controller namespace public const DEFAULT_NS = 'App\\Web\\'; @@ -42,6 +44,8 @@ class Kernel private static $ctrlNameSpace = null; // The controller file class name private static $controllerName = null; + // Fallback to index controller if the segments do not match any controller + private static bool $fallbackToIndex = false; // System environment private static $environment = ''; @@ -88,7 +92,7 @@ private static function buildControllerName(string $namespace, array $arguments, if ($routing) { $path = implode('/', $arguments); - foreach ((Configuration::get('uri.routing.routes.' . $namespace) ?: []) as $route => $controller) { + foreach (config_get('uri.routing.routes.' . $namespace, []) as $route => $controller) { if (preg_match('#' . $route . '#', $path)) { return $namespace . $controller; } @@ -165,9 +169,6 @@ private static function callSpecialCommands(): void '_pi_' => config_get('system.system_internal_methods.phpinfo'), '_springy_' => config_get('system.system_internal_methods.about'), '_system_bug_' => config_get('system.system_internal_methods.system_errors') && self::systemBugAccess(), - '_system_bug_solved_' => config_get('system.system_internal_methods.system_errors') - && self::systemBugAccess() - && preg_match('/(^[0-9a-z]{8}(,[0-9a-z]{8})*|all)$/', URI::getSegment(1, false)), default => false, }; @@ -180,10 +181,9 @@ private static function callSpecialCommands(): void ob_end_flush(); }, '_springy_' => fn () => new Core\Copyright(true), - '_system_bug_' => fn () => (new Errors())->bugList(), - '_system_bug_solved_' => fn () => (new Errors())->bugSolved(URI::getSegment(1, false)), + '_system_bug_' => fn () => (new ErrorsList())(), } - ) : new Errors(404, 'Page not found'); + ) : throw new HttpErrorNotFound(); } /** @@ -223,8 +223,8 @@ private static function checkController(string $name, array $arguments, bool $in private static function checkDevAccessDebug(): void { // Has a developer credential? - $devUser = Configuration::get('system.developer_user'); - $devPass = Configuration::get('system.developer_pass'); + $devUser = config_get('system.developer_user', ''); + $devPass = config_get('system.developer_pass', ''); if (!$devUser || !$devPass) { return; } @@ -244,8 +244,8 @@ private static function checkDevAccessDebug(): void } // Has a DBA credential? - $dbaUser = Configuration::get('system.dba_user'); - if (!Configuration::get('system.debug') || !$dbaUser) { + $dbaUser = config_get('system.dba_user', ''); + if (!config_get('system.debug', false) || !$dbaUser) { return; } @@ -288,16 +288,18 @@ private static function findControllerByNamespace(): void $arguments, true ) + // Fallback to index controller in current $arguments path if the segments do not match any controller + || (self::$fallbackToIndex && count($arguments) === 1 && self::checkController( + $namespace . 'Index', + $arguments, + false + )) ) { return; } array_pop($arguments); - - if (!count($arguments)) { - break; - } - } while (true); + } while (count($arguments) > 0); // Finds in route conf self::checkController( @@ -345,10 +347,11 @@ private static function getRouteConfiguration(): array { $host = URI::getHost(); - foreach ((Configuration::get('uri.routing.hosts') ?: []) as $route => $data) { + foreach (config_get('uri.routing.hosts', []) as $route => $data) { $pattern = sprintf('#^%s$#', $route); if (preg_match_all($pattern, $host)) { self::$tplPrefix = $data['template'] ?? []; + self::$fallbackToIndex = $data['fallbackToIndex'] ?? false; return [ 'namespace' => $data['namespace'] ?? self::DEFAULT_NS, @@ -357,9 +360,11 @@ private static function getRouteConfiguration(): array } } + self::$fallbackToIndex = config_get('uri.routing.fallbackToIndex', false); + return [ - 'namespace' => Configuration::get('uri.routing.namespace') ?: self::DEFAULT_NS, - 'segments' => Configuration::get('uri.routing.segments') ?: [], + 'namespace' => config_get('uri.routing.namespace', self::DEFAULT_NS), + 'segments' => config_get('uri.routing.segments', []), ]; } @@ -370,7 +375,7 @@ private static function getRouteConfiguration(): array */ private static function httpAuthNeeded(): void { - $auth = Configuration::get('system.authentication'); + $auth = config_get('system.authentication'); // HTTP authentication credential not defined? if ( @@ -412,26 +417,17 @@ private static function httpStartup(): void } // Send Cache-Control header - header('Cache-Control: ' . Configuration::get('system.cache-control'), true); + header('Cache-Control: ' . config_get('system.cache-control', ''), true); } /** - * Loads the .env file and merges to Kernel::$sysconf properties. - * - * The parameter $sysconf will be removed in v4.7. - * - * @param array $sysconf configuration loaded from sysconf.php file. - * - * @return void + * Loads the .env file. */ - private static function loadEnvFile(array $sysconf): void + private static function loadEnvFile(): void { $envcache = cache_dir() . DS . '.env.php'; $envfile = project_path() . DS . '.env'; - // Put legacy sysconf.php array into env - array_walk($sysconf, fn ($value, $key) => putenv(sprintf('%s=%s', $key, $value))); - if ( file_exists($envfile) && ( @@ -474,7 +470,7 @@ private static function normalizeNamePath(array $segments): string private static function setEnv(): void { $env = env('SPRINGY_ENVIRONMENT'); - $byHosts = Configuration::get('env_by_host'); + $byHosts = config_get('env_by_host', []); if (is_array($byHosts) && count($byHosts)) { $host = URI::getHost() ?: 'localhost'; @@ -499,7 +495,7 @@ private static function setEnv(): void private static function systemBugAccess(): bool { // Has a credential to system bug page? - $auth = Configuration::get('system.bug_authentication'); + $auth = config_get('system.bug_authentication', []); if (empty($auth['user']) || empty($auth['pass'])) { return true; @@ -521,22 +517,16 @@ private static function systemBugAccess(): bool /** * Starts the application. - * - * The initialization parameter $sysconf will be removed in v4.7. - * - * @param array $sysconf configuration loaded from sysconf.php file. - * - * @return void */ - public static function run(array $sysconf) + public static function run(): void { - self::loadEnvFile($sysconf); + self::loadEnvFile(); self::setEnv(); ini_set('date.timezone', env('TIMEZONE') ?: 'UTC'); ini_set('default_charset', charset()); - ini_set('display_errors', Configuration::get('system.debug') ? 1 : 0); + ini_set('display_errors', config_get('system.debug', false) ? 1 : 0); header('Content-Type: text/html; charset=' . charset(), true); // Pre start check list of application @@ -545,7 +535,7 @@ public static function run(array $sysconf) self::checkDevAccessDebug(); // System is under maintenance mode? - if (Configuration::get('system.maintenance')) { + if (config_get('system.maintenance', false)) { new Errors(503, 'The system is under maintenance'); } @@ -587,116 +577,6 @@ public static function environment(): ?string return self::$environment; } - /** - * Returns environment data. - * - * @param string $key - * - * @deprecated 4.6.0 - * - * @uses env() - * - * @return mixed - */ - public static function systemConfGlobal(string $key): mixed - { - return env($key, null); - } - - /** - * The system name. - * - * Warning! This function will be removed in the future. - * - * @deprecated 4.6.0 - * - * @uses app_name() - * - * @return string A string containing the system name. - */ - public static function systemName(): string - { - return app_name(); - } - - /** - * The system version. - * - * Warning! This function will be removed in the future. - * - * @deprecated 4.6.0 - * - * @uses app_version() - * - * @see https://semver.org - * - * @return string A string containing the system version. - */ - public static function systemVersion(): string - { - if (defined('APP_VERSION')) { - return app_version(); - } - - [$major, $minor, $patch] = is_array(env('SYSTEM_VERSION', null)) - ? env('SYSTEM_VERSION') - : explode( - '.', - (string) env('SYSTEM_VERSION', '0.0.0') - ); - - return implode('.', [$major ?? 0, $minor ?? 0, $patch ?? 0]); - } - - /** - * The project code name. - * - * @see https://en.wikipedia.org/wiki/Code_name#Project_code_name - * @deprecated 4.6.0 - * - * @uses app_codename() - * - * @return string A string containing the project code name. - */ - public static function projectCodeName(): string - { - return app_codename(); - } - - /** - * The system charset. - * - * @deprecated 4.6.0 - * - * @uses env('CHARSET') - * - * @return string A string containing the system charset. - */ - public static function charset(): string - { - return env('CHARSET') ?? 'UTF-8'; - } - - /** - * A path of the system. - * - * @param string $component the component constant. - * - * @deprecated 4.6.0 - * - * @return string A string containing the path of the component. - */ - public static function path(string $component): string - { - return match ($component) { - self::PATH_APPLICATION => app_path(), - self::PATH_CONF => config_dir(), - self::PATH_MIGRATION => migration_dir(), - self::PATH_PROJECT => project_path(), - self::PATH_VAR => var_dir(), - }; - } - /** * Adds an error code to the list of ignored errors. * @@ -770,8 +650,8 @@ public static function callErrorHook($errno, $msg, $errorId, $additionalInfo) $hook = self::$errorHooks['default']; } elseif (isset(self::$errorHooks['all'])) { $hook = self::$errorHooks['all']; - } elseif (!$hook = Configuration::get('system.system_error.hook.' . $errno)) { - $hook = Configuration::get('system.system_error.hook.default'); + } elseif (!$hook = config_get('system.system_error.hook.' . $errno)) { + $hook = config_get('system.system_error.hook.default', false); } if ($hook) { @@ -796,24 +676,6 @@ public static function setErrorHook($errno, $funcHook): void self::$errorHooks[$errno] = $funcHook; } - /** - * Gets and/or sets the root controller. - * - * @param array $cRoot if defined sets the new root controller. - * - * @deprecated 4.6.1 - * - * @return array - */ - public static function controllerRoot($cRoot = null) - { - if (!is_null($cRoot)) { - self::$tplPrefix = $cRoot; - } - - return self::$tplPrefix; - } - /** * Gets the controller namespace. * diff --git a/springy/Mail.php b/springy/Mail.php index 15a5add..9e3497d 100644 --- a/springy/Mail.php +++ b/springy/Mail.php @@ -7,60 +7,55 @@ * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT * - * @version 3.1.25 + * @deprecated 4.7.0 */ namespace Springy; +use Springy\Exceptions\SpringyException; +use Springy\Mail\Drivers\PhpMailer; +use Springy\Mail\Drivers\SendGrid; +use Springy\Mail\Mailer; + class Mail { - public const MAIL_ENGINE_PHPMAILER = 'phpmailer'; - public const MAIL_ENGINE_SWIFTMAILER = 'swiftmailer'; - public const MAIL_ENGINE_SENDGRID = 'sendgrid'; - public const MAIL_ENGINE_MIMEMESSAGE = 'mimemessage'; - - private $mailObj = null; + private Mailer $newMailer; public function __construct($mailer = null) { - if (is_null($mailer)) { - if (Configuration::get('mail.default_driver')) { - $mailer = Configuration::get('mail.default_driver'); - } else { - $mailer = key(Configuration::get('mail.mailers')); - } - } - $cfg = Configuration::get('mail.mailers.' . $mailer); + $driver = $mailer ?? config_get('mail.default_driver'); - if ($cfg == null) { - throw new \Exception('Mail configuration \'mailers.' . $mailer . '\' undefined'); - } - if (!isset($cfg['driver'])) { - throw new \Exception('Mail configuration \'driver\' undefined'); - } + if (is_null($driver)) { + $this->newMailer = new Mailer(); - // Inicializa a classe de template - switch (strtolower($cfg['driver'])) { - case self::MAIL_ENGINE_PHPMAILER: - $this->mailObj = new Mail\PHPMailerDriver($cfg); - break; - case self::MAIL_ENGINE_SWIFTMAILER: - $this->mailObj = new Mail\SwiftMailerDriver($cfg); - break; - case self::MAIL_ENGINE_SENDGRID: - $this->mailObj = new Mail\SendGridDriver($cfg); - break; - case self::MAIL_ENGINE_MIMEMESSAGE: - $this->mailObj = new Mail\MimeMessageDriver($cfg); - break; - default: - throw new \Exception('Mail driver invalid'); - } + return; + }; + + $cfg = config_get('mail.mailers.' . $driver) + ?? throw new SpringyException('Mail configuration \'mailers.' . $driver . '\' undefined'); + + match ($cfg['driver'] ?? 'undefined') { + 'phpmailer' => $this->startPhpMailer($cfg), + 'sendgrid' => $this->startSendGrid($cfg), + 'swiftmailer' => throw new SpringyException('SwiftMailer driver not implemented anymore'), + 'mimemessage' => throw new SpringyException('MimeMessage driver not implemented anymore'), + 'undefined' => throw new SpringyException('Mimee driver undefined'), + default => throw new SpringyException('Mail configuration driver unsupported'), + }; + + $this->newMailer = new Mailer(); + } + + private function startPhpMailer(array $cfg): void + { + config_set('mail.driver', PhpMailer::class); + config_set('mail.settings', $cfg); } - public function __destruct() + private function startSendGrid(array $cfg): void { - unset($this->mailObj); + config_set('mail.driver', SendGrid::class); + config_set('mail.settings', $cfg); } /** @@ -68,7 +63,7 @@ public function __destruct() */ public function addHeader($header, $value) { - $this->mailObj->addHeader($header, $value); + $this->newMailer->addHeader($header, $value); } /** @@ -84,7 +79,7 @@ public function setHeader($header, $value) */ public function setTemplate($name) { - $this->mailObj->setTemplate($name); + $this->newMailer->setTemplateId($name); } /** @@ -92,7 +87,7 @@ public function setTemplate($name) */ public function addTemplateVar($name, $value) { - $this->mailObj->addTemplateVar($name, $value); + $this->newMailer->addTemplateVar($name, $value); } /** @@ -109,21 +104,15 @@ public function addTemplateVar($name, $value) */ public function to($email, $name = '') { - // Verifica se há a entrada forçando o envio de todos os emails para um destinatário específico - if (Configuration::get('mail.mails_go_to')) { - $email = Configuration::get('mail.mails_go_to'); - $name = ''; - } - if (is_array($email)) { foreach ($email as $mail => $name) { - $this->mailObj->addTo($mail, $name); + $this->newMailer->addTo($mail, $name); } - } else { - $this->mailObj->addTo($email, $name); + + return; } - return true; + $this->newMailer->addTo($email, $name); } /** @@ -133,13 +122,13 @@ public function cc($email, $name = '') { if (is_array($email)) { foreach ($email as $mail => $name) { - $this->mailObj->addCC($mail, $name); + $this->newMailer->addCC($mail, $name); } - } else { - $this->mailObj->addCC($email, $name); + + return; } - return true; + $this->newMailer->addCC($email, $name); } /** @@ -149,13 +138,13 @@ public function bcc($email, $name = '') { if (is_array($email)) { foreach ($email as $mail => $name) { - $this->mailObj->addBCC($mail, $name); + $this->newMailer->addBCC($mail, $name); } - } else { - $this->mailObj->addBCC($email, $name); + + return; } - return true; + $this->newMailer->addBCC($email, $name); } /** @@ -163,9 +152,7 @@ public function bcc($email, $name = '') */ public function from($email, $name = '') { - $this->mailObj->setFrom($email, $name); - - return true; + $this->newMailer->setFrom($email, $name); } /** @@ -173,9 +160,7 @@ public function from($email, $name = '') */ public function subject($subject) { - $this->mailObj->setSubject($subject); - - return true; + $this->newMailer->setSubject($subject); } /** @@ -184,10 +169,10 @@ public function subject($subject) public function body($html = '', $text = '') { if ($text) { - $this->mailObj->setAlternativeBody($text); + $this->newMailer->setAlternativeBody($text); } if ($html) { - $this->mailObj->setBody($html); + $this->newMailer->setBody($html, true); } } @@ -196,13 +181,7 @@ public function body($html = '', $text = '') */ public function addAttach($path, $name = '', $type = '', $encoding = 'base64') { - if (is_array($path)) { - $name = $path['name']; - $type = $path['type']; - $path = $path['tmp_name']; - } - - $this->mailObj->addAttachment($path, $name, $type, $encoding); + $this->newMailer->addAttachment($path, $name, $type, $encoding); } /** @@ -212,17 +191,15 @@ public function addAttach($path, $name = '', $type = '', $encoding = 'base64') */ public function addCategory($category) { - $this->mailObj->addCategory($category); + $this->newMailer->addCategory($category); } /** * Sends the message. - * - * @return mixed */ public function send() { - return $this->mailObj->send(); + return $this->newMailer->send()->getLastError(); } /** diff --git a/springy/Mail/Drivers/MailDriverInterface.php b/springy/Mail/Drivers/MailDriverInterface.php new file mode 100644 index 0000000..cb26863 --- /dev/null +++ b/springy/Mail/Drivers/MailDriverInterface.php @@ -0,0 +1,117 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Mail\Drivers; + +interface MailDriverInterface +{ + /** + * Adds a file to be attached to the e-mail. + * + * @param string $path full pathname to the attachment. + * @param string $name override the attachment name. + * @param string $type MIME type/file extension type. + */ + public function addAttachment(string $path, string $name = '', string $type = ''): void; + + /** + * Adds an address to the 'BCC' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function addBcc(string $email, string $name = ''): void; + + /** + * Adds a category to the e-mail. + */ + public function addCategory(string $category): void; + + /** + * Adds an address to the 'CC' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function addCc(string $email, string $name = ''): void; + + /** + * Adds a standard email message header. + * + * @param string $header + * @param string $value + */ + public function addHeader(string $header, string $value): void; + + /** + * Adds value to a template variable. + * + * @param string $name name of the template variable. + * @param mixed $value the value. + */ + public function addTemplateVar(string $name, mixed $value): void; + + /** + * Adds an address to the 'To' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional) + */ + public function addTo(string $email, string $name = ''): void; + + /** + * Gets the last send error message. + * + * @return string + */ + public function getLastError(): string; + + /** + * Sends the e-mail message. + */ + public function send(): void; + + /** + * Sets the alternative plain-text message body for old message readers. + * + * @param string $text + */ + public function setAlternativeBody(string $text): void; + + /** + * Adds message content body. + * + * @param string $body HTML ou text message body. + * @param bool $html set true if body is HTML ou false if plain text. + */ + public function setBody(string $body, bool $html = true): void; + + /** + * Sets the 'From' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function setFrom(string $email, string $name = ''): void; + + /** + * Sets the mail subject. + * + * @param string $subject the subject text. + */ + public function setSubject(string $subject): void; + + /** + * Sets a transactional template for this email. + * + * @param string $tid the id of the template. + */ + public function setTemplateId(string $tid): void; +} diff --git a/springy/Mail/Drivers/PhpMailer.php b/springy/Mail/Drivers/PhpMailer.php new file mode 100644 index 0000000..9d54a1c --- /dev/null +++ b/springy/Mail/Drivers/PhpMailer.php @@ -0,0 +1,169 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + * + * @see https://github.com/PHPMailer/PHPMailer + * + * The PHPMailer library is not a dependency of this project. + * This driver is implemented only as a facility to the developers. + * + * You must add the PHPMailer library as dependency of your project + * by adding it in your project's composer.json file like this: + * + * { + * "require": { + * "phpmailer/phpmailer": "^6.10.0" + * } + * } + * + * Or install it yourself with the following command line: + * + * $ composer require "phpmailer/phpmailer:^6.10.0" + */ + +namespace Springy\Mail\Drivers; + +use PHPMailer\PHPMailer\PHPMailer as PHPMailerDriver; +use Springy\Exceptions\SpringyException; + +/** + * Driver class for use with PHPMailer class. + */ +class PhpMailer implements MailDriverInterface +{ + private PHPMailerDriver $mail; + private string $error; + + public function __construct(array $settings) + { + if (!isset($settings['protocol'])) { + throw new SpringyException('Mail configuration "protocol" undefined'); + } + + $this->mail = new PHPMailerDriver(true); + $this->mail->CharSet = config_get('main.charset', 'UTF-8'); + $this->error = ''; + + $this->setProtocol($settings); + } + + public function addAttachment(string $path, string $name = '', string $type = ''): void + { + $this->mail->addAttachment($path, $name, 'base64', $type); + } + + public function addBcc(string $email, string $name = ''): void + { + $this->mail->addBcc($email, $name); + } + + public function addCategory(string $category): void + { + throw new SpringyException('Mail categories not supported by this driver'); + } + + public function addCc(string $email, string $name = ''): void + { + $this->mail->addCc($email, $name); + } + + public function addHeader(string $header, string $value): void + { + $this->mail->addCustomHeader($header, $value); + } + + public function addTemplateVar(string $name, mixed $value): void + { + throw new SpringyException('Mail template variables not supported by this driver'); + } + + public function addTo(string $email, string $name = ''): void + { + $this->mail->addAddress($email, $name); + } + + public function getLastError(): string + { + return $this->error; + } + + public function send(): void + { + $this->error = ''; + + try { + if (!$this->mail->send()) { + $this->error = $this->mail->ErrorInfo; + } + } catch (\Throwable $err) { + $this->error = $err->getCode() . ': ' . $err->getMessage(); + } + } + + public function setAlternativeBody(string $text): void + { + $this->mail->AltBody = $text; + } + + public function setBody(string $body, bool $html = true): void + { + $html ? $this->mail->msgHTML($body) : $this->setAlternativeBody($body); + } + + public function setFrom(string $email, string $name = ''): void + { + $this->mail->setFrom($email, $name); + } + + /** + * Sets the mail protocol. + * + * @param array $config + * + * @throws SpringyException + */ + private function setProtocol(array $config): void + { + if ($config['protocol'] == 'smtp') { + if (!isset($config['host'])) { + throw new SpringyException('Mail configuration "host" undefined'); + } + + $this->mail->isSMTP(); + $this->mail->SMTPDebug = $config['debug'] ?? false; + $this->mail->Debugoutput = $config['debugoutput'] ?? 'html'; + $this->mail->Host = $config['host']; + $this->mail->Port = $config['port'] ?? 25; + $this->mail->SMTPAuth = $config['authenticated'] ?? false; + $this->mail->SMTPSecure = $config['cryptography'] ?? ''; + + if ($this->mail->SMTPAuth) { + $this->mail->Username = $config['username'] ?? ''; + $this->mail->Password = $config['password'] ?? ''; + } + + return; + } elseif ($config['protocol'] == 'sendmail') { + $this->mail->isSendmail(); + + return; + } + + throw new SpringyException('Unsuported mail protocol'); + } + + public function setSubject(string $subject): void + { + $this->mail->Subject = $subject; + } + + public function setTemplateId(string $tid): void + { + throw new SpringyException('Mail templates not supported by this driver'); + } +} diff --git a/springy/Mail/Drivers/SendGrid.php b/springy/Mail/Drivers/SendGrid.php new file mode 100644 index 0000000..aded8cd --- /dev/null +++ b/springy/Mail/Drivers/SendGrid.php @@ -0,0 +1,138 @@ + + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + * + * @see https://github.com/sendgrid/sendgrid-php + * + * The SendGrid PHP SDK library is not a dependency of this project. + * This driver is implemented only as a facility to the developers. + * + * You must add the SendGrid PHP SDK library as dependency of your project + * by adding it in your project's composer.json file like this: + * + * { + * "require": { + * "sendgrid/sendgrid": "~7" + * } + * } + * + * Or install it yourself with the following command line: + * + * $ composer require "sendgrid/sendgrid:~7" + */ + +namespace Springy\Mail\Drivers; + +use SendGrid as SendGridAPI; +use SendGrid\Mail\Mail; +use Springy\Exceptions\SpringyException; +use Throwable; + +class SendGrid implements MailDriverInterface +{ + private SendGridAPI $sendgrid; // the SendGrid API object + private Mail $mail; // the SendGrid mail transport object + private string $error; + + public function __construct(array $settings) + { + $apikey = $settings['apikey'] ?? false; + $options = $settings['options'] ?? []; + + if (!$apikey) { + throw new SpringyException('SendGrid API key undefined'); + } elseif (!is_array($options)) { + throw new SpringyException('Invalid SendGrid configuration options'); + } + + $this->sendgrid = new SendGridAPI($apikey, $options); + $this->mail = new Mail(); + $this->error = ''; + } + + public function addAttachment(string $path, string $name = '', string $type = ''): void + { + $fileEncoded = file_get_contents($path); + $this->mail->addAttachment($fileEncoded, $type, $name); + } + + public function addBcc(string $email, string $name = ''): void + { + $this->mail->addBcc($email, $name); + } + + public function addCategory(string $category): void + { + $this->mail->addCategory($category); + } + + public function addCc(string $email, string $name = ''): void + { + $this->mail->addCc($email, $name); + } + + public function addHeader(string $header, string $value): void + { + $this->mail->addHeader($header, $value); + } + + public function addTemplateVar(string $name, mixed $value): void + { + $this->mail->addSubstitution($name, $value); + } + + public function addTo(string $email, string $name = ''): void + { + $this->mail->addTo($email, $name); + } + + public function getLastError(): string + { + return $this->error; + } + + public function send(): void + { + $this->error = ''; + + try { + $response = $this->sendgrid->send($this->mail); + $this->error = $response->body(); + } catch (Throwable $err) { + $this->error = $err->getCode() + . ' - ' . $err->getMessage() + . ' at ' . $err->getFile() + . ' (' . $err->getLine() . ')'; + } + } + + public function setAlternativeBody(string $text): void + { + $this->mail->addContent('text/plain', $text); + } + + public function setBody(string $body, bool $html = true): void + { + $this->mail->addContent($html ? 'text/html' : 'text/plain', $body); + } + + public function setFrom(string $email, string $name = ''): void + { + $this->mail->setFrom($email, $name); + } + + public function setSubject(string $subject): void + { + $this->mail->setSubject($subject); + } + + public function setTemplateId(string $tid): void + { + $this->mail->setTemplateId($tid); + } +} diff --git a/springy/Mail/MailDriverInterface.php b/springy/Mail/MailDriverInterface.php deleted file mode 100644 index 8ec348d..0000000 --- a/springy/Mail/MailDriverInterface.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 2.1.6 - */ - -namespace Springy\Mail; - -/** - * Interface for mail drivers. - * - * This class is a interface for construction of mail drivers. - */ -interface MailDriverInterface -{ - /** - * Add a standard email message header. - */ - public function addHeader($header, $value); - - /** - * Add an address to 'To' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addTo($email, $name = ''); - - /** - * Add an address to 'BCC' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addBCC($email, $name = ''); - - /** - * Add an address to 'CC' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addCC($email, $name = ''); - - /** - * Add a file to be attached to the e-mail. - * - * @param string $path full pathname to the attachment - * @param string $name override the attachment name (optional) - * @param string $type MIME type/file extension type (optional) - * @param string $encoding file enconding (optional) - */ - public function addAttachment($path, $name = '', $type = '', $encoding = 'base64'); - - /** - * Add a category to the e-mail. - * - * @param string $category the category - */ - public function addCategory($category); - - /** - * Set the 'From' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function setFrom($email, $name = ''); - - /** - * Set the mail subject. - * - * @param string $subject the subject text - */ - public function setSubject($subject); - - /** - * Set the message bo. - * - * @param string $body HTML ou text message body - * @param string $html set true if body is HTML ou false if plain text - */ - public function setBody($body, $html = true); - - /** - * Set the alternative plain-text message body for old message readers. - */ - public function setAlternativeBody($text); - - /** - * Set a template for this email. - */ - public function setTemplate($name); - - /** - * Add value to a template variable. - */ - public function addTemplateVar($name, $value); - - /** - * Send the mail message. - * - * @return mixed The error message or a empty string if success. - */ - public function send(); -} diff --git a/springy/Mail/Mailer.php b/springy/Mail/Mailer.php new file mode 100644 index 0000000..6f015eb --- /dev/null +++ b/springy/Mail/Mailer.php @@ -0,0 +1,310 @@ + + * @author Lucas Cardozo + * @license https://github.com/springy-framework/core/blob/master/LICENSE MIT + */ + +namespace Springy\Mail; + +use Springy\Exceptions\SpringyException; +use Springy\Mail\Drivers\MailDriverInterface; + +class Mailer +{ + protected array $attachments; + protected array $bcc; + protected string $bodyHtml; + protected string $bodyPlain; + protected array $categories; + protected array $headers; + protected array $tos; + protected array $vars; + protected array $vcc; + protected string $fakeTo; + protected string $templateId; + protected string $lastError; + + /** @var object the mailer driver object */ + protected $mailObj; + + public function __construct( + protected string $fromEmail = '', + protected string $fromName = '', + protected string $subject = '' + ) { + $this->attachments = []; + $this->bcc = []; + $this->bodyHtml = ''; + $this->bodyPlain = ''; + $this->categories = []; + $this->headers = []; + $this->tos = []; + $this->vars = []; + $this->vcc = []; + $this->fakeTo = config_get('mail.fake_to', ''); + $this->templateId = ''; + $this->lastError = ''; + + $driverClass = config_get('mail.driver'); + + if (!is_string($driverClass)) { + throw new SpringyException('Invalid mail driver definition'); + } elseif (!class_exists($driverClass)) { + throw new SpringyException('Mail driver class not found'); + } elseif (is_null(config_get('mail.settings'))) { + throw new SpringyException('Mail driver configuration settings not defined'); + } + + $errorsTo = config_get('mail.errors_go_to', ''); + + if ($errorsTo !== '') { + $this->addHeader('Errors-To', $errorsTo); + } + } + + /** + * Adds a file to be attached to the e-mail. + * + * @param string $path full pathname to the attachment. + * @param string $name override the attachment name. + * @param string $type MIME type/file extension type. + */ + public function addAttachment(string $path, string $name = '', string $type = ''): self + { + $this->attachments[] = [ + 'path' => $path, + 'name' => $name, + 'type' => $type, + ]; + + return $this; + } + + /** + * Adds an address to the 'BCC' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function addBcc(string $email, string $name = ''): self + { + if (!$this->fakeTo) { + $this->bcc[] = [ + 'email' => $email, + 'name' => $name, + ]; + } + + return $this; + } + + /** + * Adds a category to the e-mail. + */ + public function addCategory(string $category): self + { + $this->categories[] = $category; + + return $this; + } + + /** + * Adds an address to the 'CC' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function addCc(string $email, string $name = ''): self + { + if (!$this->fakeTo) { + $this->vcc[] = [ + 'email' => $email, + 'name' => $name, + ]; + } + + return $this; + } + + /** + * Adds an email message header. + */ + public function addHeader(string $header, string $value): self + { + $this->headers[] = [ + 'key' => $header, + 'val' => $value, + ]; + + return $this; + } + + /** + * Adds value to a template variable. + * + * @param string $name name of the template variable. + * @param mixed $value the value. + */ + public function addTemplateVar(string $name, mixed $value): self + { + $this->vars[] = [ + 'name' => $name, + 'val' => $value, + ]; + + return $this; + } + + /** + * Adds an address to the 'To' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional) + */ + public function addTo(string $email, string $name = ''): self + { + if (!$this->fakeTo) { + $this->tos[] = [ + 'email' => $email, + 'name' => $name, + ]; + } + + return $this; + } + + /** + * Build the mailer object driver. + * + * @throws SpringyException + */ + private function createDriver(): MailDriverInterface + { + $driver = new (config_get('mail.driver'))(config_get('mail.settings')); + + if (!$driver instanceof MailDriverInterface) { + throw new SpringyException('Invalid mail driver definition'); + } + + return $driver; + } + + /** + * Gets the last send error message. + */ + public function getLastError(): string + { + return $this->lastError; + } + + /** + * Sends the mail message. + */ + public function send(): self + { + $driver = $this->createDriver(); + $driver->setFrom($this->fromEmail, $this->fromName); + $driver->setSubject($this->subject); + + if ($this->bodyHtml !== '') { + $driver->setBody($this->bodyHtml, true); + } + if ($this->bodyPlain !== '') { + $driver->setAlternativeBody($this->bodyPlain); + } + + array_walk($this->headers, fn (array $header) => $driver->addHeader($header['key'], $header['val'])); + $this->fakeTo + ? $this->mailObj->addTo($this->fakeTo) + : array_walk($this->tos, fn (array $toa) => $driver->addTo($toa['email'], $toa['name'])); + array_walk($this->bcc, fn (array $bcc) => $driver->addBcc($bcc['email'], $bcc['name'])); + array_walk($this->vcc, fn (array $vcc) => $driver->addCc($vcc['email'], $vcc['name'])); + array_walk($this->categories, fn (string $category) => $driver->addCategory($category)); + array_walk( + $this->attachments, + fn (array $attachment) => $driver->addAttachment( + $attachment['path'], + $attachment['name'], + $attachment['type'] + ) + ); + + if ($this->templateId !== '') { + $driver->setTemplateId($this->templateId); + array_walk($this->vars, fn (array $var) => $driver->addTemplateVar($var['name'], $var['val'])); + } + + $driver->send(); + $this->lastError = $driver->getLastError(); + + return $this; + } + + /** + * Sets the alternative plain-text message body for old message readers. + */ + public function setAlternativeBody(string $text): self + { + $this->bodyPlain = $text; + + return $this; + } + + /** + * Adds message content body. + * + * @param string $body HTML ou text message body. + * @param bool $html set true if body is HTML ou false if plain text. + */ + public function setBody(string $body, bool $html): self + { + $html + ? $this->bodyHtml = $body + : $this->bodyPlain = $body; + + return $this; + } + + /** + * Sets the 'From' field. + * + * @param string $email the email address. + * @param string $name the name of the person (optional). + */ + public function setFrom(string $email, string $name = ''): self + { + $this->fromEmail = $email; + $this->fromName = $name; + + return $this; + } + + /** + * Sets the mail subject. + * + * @param string $subject the subject text. + */ + public function setSubject(string $subject): self + { + $this->subject = $subject; + + return $this; + } + + /** + * Sets a mailing engine template for this email. + * + * @param string $tid the id of the template. + */ + public function setTemplateId($tid): self + { + $this->templateId = $tid; + + return $this; + } +} diff --git a/springy/Mail/MimeMessageDriver.php b/springy/Mail/MimeMessageDriver.php deleted file mode 100644 index 67086bb..0000000 --- a/springy/Mail/MimeMessageDriver.php +++ /dev/null @@ -1,292 +0,0 @@ - - * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 1.1.10 - */ - -namespace Springy\Mail; - -use Springy\Configuration; -use Springy\Kernel; - -/** - * Driver class for sent mail using MIME e-mail message class developed by - * Manuel Lemos. - * - * WARNING! This class was not tested yet. Because MIME e-mail message classes - * are not into Packagist repository we can not install it via Composer. Then - * you will need download and install it by yourself and create an autoload to - * it. There are commented lines in __construct method with require_once for - * each file needed. - * - * This classe is a driver used by Springy\Mail classe. - * - * Do not use it directly. - */ -class MimeMessageDriver implements MailDriverInterface -{ - private $mailObj = null; - private $sendmail = false; - private $textMessage = ''; - private $alternativeParts = []; - private $headers = [ - 'Sender' => '', - 'Subject' => '', - ]; - private $mailHeaders = [ - 'From' => '', - 'Sender' => '', - 'To' => [], - 'Cc' => [], - 'Bcc' => [], - ]; - - /** - * Constructor method. - * - * @param array $cfg array with de configuration. - */ - public function __construct($cfg) - { - if (!isset($cfg['protocol'])) { - throw new \Exception('Mail configuration \'protocol\' undefined'); - } - - if ($cfg['protocol'] == 'smtp') { - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'email_message.php'; - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'smtp_message.php'; - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'Smtp' . DS . 'smtp.php'; - // if (Configuration::get('mail', 'ssl') || Configuration::get('mail', 'starttls')) { - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'Sasl' . DS . 'sasl.php'; - // } - $this->mailObj = new \smtp_message_class(); - - $this->mailObj->smtp_host = isset($cfg['host']) ? $cfg['host'] : 'localhost'; - $this->mailObj->smtp_port = isset($cfg['port']) ? $cfg['port'] : 25; - $this->mailObj->smtp_ssl = isset($cfg['ssl']) ? $cfg['ssl'] : false; - $this->mailObj->smtp_start_tls = isset($cfg['starttls']) ? $cfg['starttls'] : false; - $this->mailObj->smtp_direct_delivery = isset($cfg['direct_delivery']) ? $cfg['direct_delivery'] : false; - $this->mailObj->smtp_exclude_address = isset($cfg['exclude_address']) ? $cfg['exclude_address'] : ''; - $this->mailObj->smtp_user = isset($cfg['username']) ? $cfg['username'] : ''; - $this->mailObj->smtp_password = isset($cfg['password']) ? $cfg['password'] : ''; - $this->mailObj->smtp_realm = isset($cfg['realm']) ? $cfg['realm'] : ''; - $this->mailObj->smtp_workstation = isset($cfg['workstation']) ? $cfg['workstation'] : ''; - $this->mailObj->smtp_pop3_auth_host = isset($cfg['auth_host']) ? $cfg['auth_host'] : null; - $this->mailObj->smtp_debug = isset($cfg['debug']) ? $cfg['debug'] : 0; - $this->mailObj->smtp_html_debug = isset($cfg['html_debug']) ? $cfg['html_debug'] : 0; - - if (isset($cfg['proxyhost']) && $cfg['proxyhost']) { - $this->mailObj->smtp_http_proxy_host_name = $cfg['proxyhost']; - $this->mailObj->smtp_http_proxy_host_port = $cfg['proxyport']; - } - } elseif ($cfg['protocol'] == 'sendmail') { - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'email_message.php'; - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'sendmail_message.php'; - $this->mailObj = new \sendmail_message_class(); - - $this->mailObj->delivery_mode = \SENDMAIL_DELIVERY_DEFAULT; - $this->mailObj->bulk_mail_delivery_mode = \SENDMAIL_DELIVERY_QUEUE; - $this->mailObj->sendmail_arguments = ''; - - $this->sendmail = true; - } else { - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'email_message.php'; - // require_once Kernel::path(Kernel::PATH_VENDOR) . DS . 'MimeMessage' . DS . 'sendmail_message.php'; - $this->mailObj = new \email_message_class(); - - $this->mailObj->smtp_workstation = isset($cfg['workstation']) ? $cfg['workstation'] : ''; - } - - if (Configuration::get('mail', 'errors_go_to')) { - $this->mailObj->SetEncodedEmailHeader('Errors-To', Configuration::get('mail', 'errors_go_to'), ''); - } - } - - /** - * Add a standard email message header. - */ - public function addHeader($header, $value) - { - $this->mailObj->SetEncodedHeader($header, $value, charset()); - } - - /** - * Set the value of an header that is meant to represent the e-mail address. - */ - public function setEmailHeader($header, $email, $name = '') - { - if (is_array($email)) { - $this->mailObj->SetMultipleEncodedEmailHeader($header, $email, charset()); - } else { - $this->mailObj->SetEncodedEmailHeader($header, $email, $name, charset()); - } - } - - /** - * Add an address to 'To' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addTo($email, $name = '') - { - $this->mailHeaders['To'][$email] = $name; - } - - /** - * Add an address to 'BCC' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addBCC($email, $name = '') - { - $this->mailHeaders['Bcc'][$email] = $name; - } - - /** - * Add an address to 'CC' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function addCC($email, $name = '') - { - $this->mailHeaders['Cc'][$email] = $name; - } - - /** - * Add a file to be attached to the e-mail. - * - * @param string $path full pathname to the attachment - * @param string $name override the attachment name (optional) - * @param string $type MIME type/file extension type (optional) - * @param string $encoding file enconding (optional) - */ - public function addAttachment($path, $name = '', $type = '', $encoding = 'base64') - { - $this->mailObj->AddFilePart([ - 'FileName' => $path, - 'Name' => $name, - 'Content-Type' => $type, - ]); - } - - /** - * Add a category to the e-mail. - * - * @param string $category the category - */ - public function addCategory($category) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Set the 'From' field. - * - * @param string $email the email address - * @param string $name the name of the person (optional) - */ - public function setFrom($email, $name = '') - { - $this->mailHeaders['From'] = [$email, $name]; - $this->headers['Sender'] = [$email, $name]; - } - - /** - * Set the mail subject. - * - * @param string $subject the subject text - */ - public function setSubject($subject) - { - $this->headers['Subject'] = $subject; - } - - /** - * Set the message bo. - * - * @param string $body HTML ou text message body - * @param string $html set true if body is HTML ou false if plain text - */ - public function setBody($body, $html = true) - { - if ($html) { - $this->mailObj->CreateQuotedPrintableHTMLPart($html, charset(), $htmlPart); - $this->alternativeParts[] = $htmlPart; - } else { - $this->textMessage = $text; - $this->mailObj->CreateQuotedPrintableTextPart($text, charset(), $textPart); - $this->alternativeParts[] = $textPart; - } - } - - /** - * Set the alternative plain-text message body for old message readers. - */ - public function setAlternativeBody($text) - { - $this->setBody($text, false); - } - - /** - * Set a template for this email. - */ - public function setTemplate($name) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Add value to a template variable. - */ - public function addTemplateVar($name, $value) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Send the mail message. - * - * @return mixed The error message or a empty string if success. - */ - public function send() - { - $error = false; - - foreach ($this->mailHeaders as $header => $value) { - if (!empty($value)) { - $this->setEmailHeader($header, $value); - } - } - foreach ($this->headers as $header => $value) { - if (!empty($value)) { - $this->addHeader($header, $value); - } - } - - if ($this->sendmail) { - $error = $this->mailObj->Mail( - key($this->mailHeaders['To']), - $this->headers['Subject'], - $this->textMessage, - '', - '' - ); - } else { - $this->mailObj->AddAlternativeMultipart($this->alternativeParts); - $error = $this->mailObj->Send(); - } - - return $error; - } -} diff --git a/springy/Mail/PHPMailerDriver.php b/springy/Mail/PHPMailerDriver.php deleted file mode 100644 index 75484d7..0000000 --- a/springy/Mail/PHPMailerDriver.php +++ /dev/null @@ -1,248 +0,0 @@ - - * - * @see https://github.com/PHPMailer/PHPMailer - * - * @version 1.2.2 - */ - -namespace Springy\Mail; - -use PHPMailer\PHPMailer\PHPMailer; -use Springy\Configuration; - -/** - * Driver class for use with PHPMaier. - * - * Note: This classe is a driver used by Springy\Mail classe. - * Do not use it directly. - */ -class PHPMailerDriver implements MailDriverInterface -{ - private $mailObj = null; - - /** - * Constructor method. - * - * @param array $cfg configurations array. - */ - public function __construct($cfg) - { - if (!isset($cfg['protocol'])) { - throw new \Exception('Mail configuration \'protocol\' undefined'); - } - - $this->mailObj = new PHPMailer(); - $this->mailObj->CharSet = charset(); - - if ($cfg['protocol'] == 'smtp') { - if (!isset($cfg['host'])) { - throw new \Exception('Mail configuration \'host\' undefined'); - } - - $this->mailObj->isSMTP(); - $this->mailObj->SMTPDebug = isset($cfg['debug']) ? $cfg['debug'] : false; - $this->mailObj->Debugoutput = isset($cfg['debugoutput']) ? $cfg['debugoutput'] : 'html'; - $this->mailObj->Host = $cfg['host']; - $this->mailObj->Port = isset($cfg['port']) ? $cfg['port'] : 25; - $this->mailObj->SMTPSecure = isset($cfg['cryptography']) ? $cfg['cryptography'] : ''; - $this->mailObj->SMTPAuth = isset($cfg['authenticated']) ? $cfg['authenticated'] : false; - if ($this->mailObj->SMTPAuth) { - $this->mailObj->Username = isset($cfg['username']) ? $cfg['username'] : ''; - $this->mailObj->Password = isset($cfg['password']) ? $cfg['password'] : ''; - } - } elseif ($cfg['protocol'] == 'sendmail') { - $this->mailObj->isSendmail(); - } else { - throw new \Exception('Unsuported mail protocol'); - } - - if (Configuration::get('mail', 'errors_go_to')) { - $this->mailObj->Sender = Configuration::get('mail', 'errors_go_to'); - } - - $this->mailObj->addCustomHeader('Errors-To', Configuration::get('mail', 'errors_go_to')); - } - - /** - * Adds a standard email message header. - * - * @param string $header - * @param string $value - * - * @return void - */ - public function addHeader($header, $value) - { - $this->mailObj->addCustomHeader($header, $value); - } - - /** - * Adds an address to 'To' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional) - * - * @return void - */ - public function addTo($email, $name = '') - { - $this->mailObj->addAddress($email, $name); - } - - /** - * Adds an address to 'BCC' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function addBCC($email, $name = '') - { - $this->mailObj->addBcc($email, $name); - } - - /** - * Adds an address to 'CC' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function addCC($email, $name = '') - { - $this->mailObj->addCc($email, $name); - } - - /** - * Adds a file to be attached to the e-mail. - * - * @param string $path full pathname to the attachment. - * @param string $name override the attachment name (optional). - * @param string $type MIME type/file extension type (optional). - * @param string $encoding file enconding (unused). - * - * @return void - */ - public function addAttachment($path, $name = '', $type = '', $encoding = 'base64') - { - $this->mailObj->addAttachment($path, $name, $encoding, $type); - } - - /** - * Throws Exception because this method is not supported by PHPMailer. - * - * @param string $category the category. - * - * @throws Exception - * - * @return void - */ - public function addCategory($category) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Sets the 'From' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function setFrom($email, $name = '') - { - $this->mailObj->setFrom($email, $name); - } - - /** - * Sets the mail subject. - * - * @param string $subject the subject text. - * - * @return void - */ - public function setSubject($subject) - { - $this->mailObj->Subject = $subject; - } - - /** - * Adds message content body. - * - * @param string $body HTML ou text message body. - * @param bool $html set true if body is HTML ou false if plain text. - * - * @return void - */ - public function setBody($body, $html = true) - { - $this->mailObj->isHTML($html); - $this->mailObj->Body = $body; - } - - /** - * Sets the alternative plain-text message body for old message readers. - * - * @param string $text - * - * @return void - */ - public function setAlternativeBody($text) - { - $this->mailObj->AltBody = $text; - } - - /** - * Throws Exception because this method is not supported by PHPMailer. - * - * @param string $name the id of the template. - * - * @throws Exception - * - * @return void - */ - public function setTemplate($name) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Throws Exception because this method is not supported by PHPMailer. - * - * @param string $name name of the template variable. - * @param string $value the value. - * - * @throws Exception - * - * @return void - */ - public function addTemplateVar($name, $value) - { - throw new \Exception('Resourse unavailable.'); - } - - /** - * Sends the mail message. - * - * @return string The error message or a empty string if success. - */ - public function send() - { - $error = false; - - if (!$this->mailObj->send()) { - $error = $this->mailObj->ErrorInfo; - } - - return $error; - } -} diff --git a/springy/Mail/SendGridDriver.php b/springy/Mail/SendGridDriver.php deleted file mode 100644 index e5c1cbb..0000000 --- a/springy/Mail/SendGridDriver.php +++ /dev/null @@ -1,232 +0,0 @@ - - * - * @see https://github.com/sendgrid/sendgrid-php - * - * @version 3.2.10 - */ - -namespace Springy\Mail; - -use Springy\Configuration; - -/** - * Driver class for use with SendGrid v7 class for integration with SendGrid API v3. - * - * Note: This classe is a driver used by Springy\Mail classe. - * Do not use it directly. - */ -class SendGridDriver implements MailDriverInterface -{ - private $sendgrid = null; - private $mailObj = null; - - /** - * Constructor method. - * - * @param array $cfg configurations array. - */ - public function __construct($cfg) - { - if (!isset($cfg['apikey']) || empty($cfg['apikey'])) { - throw new \Exception('Mail configuration SendGrid authentication undefined'); - } - - $options = (isset($cfg['options']) && is_array($cfg['options'])) ? $cfg['options'] : []; - - $this->sendgrid = new \SendGrid($cfg['apikey'], $options); - $this->mailObj = new \SendGrid\Mail\Mail(); - - if (Configuration::get('mail', 'errors_go_to')) { - $this->mailObj->addHeader('Errors-To', Configuration::get('mail', 'errors_go_to')); - } - } - - /** - * Adds a standard email message header. - * - * @param string $header - * @param string $value - * - * @return void - */ - public function addHeader($header, $value) - { - $this->mailObj->personalization[0]->addHeader($header, $value); - } - - /** - * Adds an address to 'To' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional) - * - * @return void - */ - public function addTo($email, $name = '') - { - $this->mailObj->addTo($email, $name); - } - - /** - * Adds an address to 'BCC' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function addBCC($email, $name = '') - { - $this->mailObj->addBcc($email, $name); - } - - /** - * Adds an address to 'CC' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function addCC($email, $name = '') - { - $this->mailObj->addCc($email, $name); - } - - /** - * Adds a file to be attached to the e-mail. - * - * @param string $path full pathname to the attachment. - * @param string $name override the attachment name (optional). - * @param string $type MIME type/file extension type (optional). - * @param string $encoding file enconding (unused). - * - * @return void - */ - public function addAttachment($path, $name = '', $type = '', $encoding = 'base64') - { - // $fileEncoded = base64_encode(file_get_contents($path)); - // Removed base64_encode due to bug in SDK - $fileEncoded = file_get_contents($path); - $this->mailObj->addAttachment($fileEncoded, $type, $name); - } - - /** - * Adds a category to the e-mail. - * - * @param string $category the category. - * - * @return void - */ - public function addCategory($category) - { - $this->mailObj->addCategory($category); - } - - /** - * Sets the 'From' field. - * - * @param string $email the email address. - * @param string $name the name of the person (optional). - * - * @return void - */ - public function setFrom($email, $name = '') - { - $this->mailObj->setFrom($email, $name); - } - - /** - * Sets the mail subject. - * - * @param string $subject the subject text. - * - * @return void - */ - public function setSubject($subject) - { - $this->mailObj->setSubject($subject); - } - - /** - * Adds message content body. - * - * @param string $body HTML ou text message body. - * @param bool $html set true if body is HTML ou false if plain text. - * - * @return void - */ - public function setBody($body, $html = true) - { - if ($html) { - $this->mailObj->addContent('text/html', $body); - - return; - } - - $this->mailObj->addContent('text/plain', $body); - } - - /** - * Sets the alternative plain-text message body for old message readers. - * - * @param string $text - * - * @return void - */ - public function setAlternativeBody($text) - { - $this->mailObj->addContent('text/plain', $text); - } - - /** - * Sets a transactional template for this email. - * - * @param string $name the id of the template. - * - * @return void - */ - public function setTemplate($name) - { - $this->mailObj->setTemplateId($name); - } - - /** - * Adds value to a template variable. - * - * @param string $name name of the template variable. - * @param string $value the value. - * - * @return void - */ - public function addTemplateVar($name, $value) - { - $this->mailObj->addSubstitution($name, $value); - } - - /** - * Sends the mail message. - * - * @return string The error message or a empty string if success. - */ - public function send() - { - $error = false; - - try { - $response = $this->sendgrid->send($this->mailObj); - $error = $response->body(); - } catch (\Exception $err) { - $error = $err->getCode() . ' - ' . $err->getMessage() - . ' at ' . $err->getFile() . ' (' . $err->getLine() . ')'; - } - - return $error; - } -} diff --git a/springy/Mail/SwiftMailerDriver.php b/springy/Mail/SwiftMailerDriver.php deleted file mode 100644 index 1cb0fc0..0000000 --- a/springy/Mail/SwiftMailerDriver.php +++ /dev/null @@ -1,151 +0,0 @@ - - * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 2.1.10 - */ - -namespace Springy\Mail; - -use Springy\Configuration; - -/** - * Driver class for sent mail using Swift Mailer class. - * - * This classe is a driver used by Springy\Mail classe. - * Do not use it directly. - */ -class SwiftMailerDriver implements MailDriverInterface -{ - private $mailObj = null; - - /** - * Constructor method. - * - * @param array $cfg - */ - public function __construct($cfg) - { - if (!isset($cfg['protocol'])) { - throw new \Exception('Mail configuration \'protocol\' undefined'); - } - - $this->mailObj = \Swift_Message::newInstance(); - $this->mailObj->setCharset(charset()); - - if ($cfg['protocol'] == 'smtp') { - if (!isset($cfg['host'])) { - throw new \Exception('Mail configuration \'host\' undefined'); - } - - $this->transport = \Swift_SmtpTransport::newInstance($cfg['host'], isset($cfg['port']) ? $cfg['port'] : 25); - - if (isset($cfg['cryptography'])) { - $this->transport->setEncryption($cfg['cryptography']); - } - if (isset($cfg['username']) && $cfg['username']) { - $this->transport->setUsername($cfg['username']); - $this->transport->setPassword(isset($cfg['password']) ? $cfg['password'] : ''); - } - } elseif ($cfg['protocol'] == 'sendmail') { - $this->transport = \Swift_SendmailTransport::newInstance( - isset($cfg['sendmail_path']) ? $cfg['sendmail_path'] : null - ); - } elseif ($cfg['protocol'] == 'mail') { - $this->transport = \Swift_MailTransport::newInstance(); - } else { - throw new \Exception('Unsuported mail transport agent'); - } - - if (Configuration::get('mail', 'errors_go_to')) { - $this->mailObj->setReturnPath(Configuration::get('mail', 'errors_go_to')); - } - $this->mailObj->addTextHeader('Errors-To', Configuration::get('mail', 'errors_go_to')); - } - - /** - * Add a standard email message header. - */ - public function addHeader($header, $value) - { - $this->mailObj->addTextHeader($header, $value); - } - - public function addTo($email, $name = '') - { - $this->mailObj->addTo($email, $name); - } - - public function addBCC($email, $name = '') - { - $this->mailObj->addBcc($email, $name); - } - - public function addCC($email, $name = '') - { - $this->mailObj->addCc($email, $name); - } - - public function addAttachment($path, $name = '', $type = '', $encoding = 'base64') - { - $attachment = \Swift_Attachment::fromPath($path, $type); - if ($name) { - $attachment->setFilename($name); - } - $this->mailObj->attach($attachment); - } - - public function addCategory($category) - { - throw new \Exception('Resourse unavailable.'); - } - - public function setSubject($subject) - { - $this->mailObj->setSubject($subject); - } - - public function setFrom($email, $name = '') - { - $this->mailObj->setFrom([$email => $name]); - } - - public function setBody($body, $html = true) - { - $this->mailObj->setBody($body, $html ? 'text/html' : 'text/plain'); - } - - public function setAlternativeBody($text) - { - $this->mailObj->addPart($text, 'text/plain'); - } - - public function setTemplate($name) - { - throw new \Exception('Resourse unavailable.'); - } - - public function addTemplateVar($name, $value) - { - throw new \Exception('Resourse unavailable.'); - } - - public function send() - { - $error = false; - - $mailer = \Swift_Mailer::newInstance($this->transport); - if (!$mailer->send($this->mailObj, $failures)) { - $error = $failures; - } - - return $error; - } -} diff --git a/springy/Migrator.php b/springy/Migrator.php index b27b06a..f73748d 100644 --- a/springy/Migrator.php +++ b/springy/Migrator.php @@ -7,7 +7,7 @@ * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT * - * @version 0.5.2 + * @version 0.5.3 */ namespace Springy; @@ -585,7 +585,7 @@ private function getScriptsSubdir($direction) */ private function runFile($file) { - $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + $extension = mb_strtolower(pathinfo($file, PATHINFO_EXTENSION)); switch ($extension) { case 'sql': diff --git a/springy/Model.php b/springy/Model.php index a2e3a1e..7808f0b 100644 --- a/springy/Model.php +++ b/springy/Model.php @@ -9,8 +9,6 @@ * @author Fernando Val * @author Allan Marques * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 2.10.0 */ namespace Springy; @@ -20,11 +18,6 @@ use Springy\DB\Where; use Springy\Validation\Validator; -/** - * Parent class for models. - * - * This class extends the DB class. - */ class Model extends DB { /** @var string the name of the table */ @@ -1291,7 +1284,7 @@ public function query($filter = null, array $orderby = [], $offset = 0, $limit = // Order by $order = []; foreach ($orderby as $column => $direction) { - $order[] = $column . ' ' . strtoupper($direction); + $order[] = $column . ' ' . mb_strtoupper($direction); } if (count($order)) { $sql .= ' ORDER BY ' . implode(', ', $order); diff --git a/springy/SOAP_Client.php b/springy/SOAP_Client.php index 8bac88a..1afa203 100644 --- a/springy/SOAP_Client.php +++ b/springy/SOAP_Client.php @@ -3,7 +3,7 @@ /** * SOAP Client. * - * phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + * phpcs:disable Squiz.Classes.ValidClassName.NotPascalCase * * @copyright 2007 Fernando Val * @author Fernando Val diff --git a/springy/Security/BasicHasher.php b/springy/Security/BasicHasher.php index b15ee5a..e848473 100644 --- a/springy/Security/BasicHasher.php +++ b/springy/Security/BasicHasher.php @@ -7,8 +7,6 @@ * @author Allan Marques * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 0.2.0 */ namespace Springy\Security; @@ -67,7 +65,7 @@ public function verify(string $stringToCheck, string $hash) */ public function generateHash($senha, $times = null) { - $md5 = md5(strtolower(self::SALT . $senha)); + $md5 = md5(mb_strtolower(self::SALT . $senha)); return base64_encode($md5 ^ md5($senha)); } diff --git a/springy/Session.php b/springy/Session.php index b191c5a..e859ae3 100644 --- a/springy/Session.php +++ b/springy/Session.php @@ -6,8 +6,6 @@ * @copyright 2007 Fernando Val * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 2.3.0 */ namespace Springy; @@ -187,7 +185,8 @@ private static function startDBSession(): void $res = $db->fetchNext(); self::$data = unserialize($res['session_value']); } else { - $sql = 'INSERT INTO ' . self::sessionTableName() . '(`id`, `session_value`, `updated_at`) VALUES (?, NULL, NOW())'; + $sql = 'INSERT INTO ' . self::sessionTableName() + . '(`id`, `session_value`, `updated_at`) VALUES (?, NULL, NOW())'; $db->execute($sql, [self::$sid]); self::$data = []; } diff --git a/springy/Template.php b/springy/Template.php index d9b128f..464cdc6 100644 --- a/springy/Template.php +++ b/springy/Template.php @@ -8,7 +8,7 @@ * @author Lucas Cardozo * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT * - * @version 4.3.0 + * @version 4.3.1 */ namespace Springy; @@ -37,7 +37,7 @@ public function __construct($tpl = null) } // Inicializa a classe de template - switch (strtolower($driver)) { + switch (mb_strtolower($driver)) { case self::TPL_ENGINE_SMARTY: $this->tplObj = new Template\SmartyDriver($tpl); break; @@ -45,7 +45,7 @@ public function __construct($tpl = null) $this->tplObj = new Template\TwigDriver($tpl); break; default: - throw_error('500', 'Template engine not implemented'); + throw_error(500, 'Template engine not implemented'); } $this->addUriCommonUrls(); diff --git a/springy/Template/SmartyDriver.php b/springy/Template/SmartyDriver.php index 46d2054..81fa3b5 100644 --- a/springy/Template/SmartyDriver.php +++ b/springy/Template/SmartyDriver.php @@ -15,6 +15,7 @@ namespace Springy\Template; use Smarty\Smarty; +use Springy\Exceptions\HttpErrorNotFound; use Springy\Kernel; use Springy\URI; @@ -191,7 +192,7 @@ public function setCacheLifetime($seconds) public function fetch() { if (!$this->templateExists($this->templateName)) { - throw_error(404, $this->templateName . self::TPL_NAME_SUFIX); + throw new HttpErrorNotFound($this->templateName . self::TPL_NAME_SUFIX); } // Alimenta as variáveis CONSTANTES diff --git a/springy/Template/TwigDriver.php b/springy/Template/TwigDriver.php index 16564ba..f4eeeb2 100644 --- a/springy/Template/TwigDriver.php +++ b/springy/Template/TwigDriver.php @@ -17,9 +17,12 @@ namespace Springy\Template; use Springy\Configuration; -use Springy\Errors; +use Springy\Exceptions\HttpErrorNotFound; use Springy\Kernel; use Springy\URI; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; +use Twig\TwigFunction; /** * Class driver for Twig template engine. @@ -90,8 +93,8 @@ private function __twigInstance($templatePath) } $this->templatePath = $templatePath; - $loader = new \Twig_Loader_Filesystem($templatePath); - $this->tplObj = new \Twig_Environment($loader, $this->envOptions); + $loader = new FilesystemLoader($templatePath); + $this->tplObj = new Environment($loader, $this->envOptions); } /** @@ -227,7 +230,7 @@ public function setCacheLifetime($seconds) public function fetch() { if (!$this->templateExists($this->templateName)) { - new Errors(404, $this->templateName . self::TPL_NAME_SUFIX); + throw new HttpErrorNotFound($this->templateName . self::TPL_NAME_SUFIX); } // Alimenta as variáveis CONSTANTES @@ -252,16 +255,16 @@ public function fetch() } // Inicializa a função padrão assetFile - $this->tplObj->addFunction(new \Twig_SimpleFunction('assetFile', [$this, 'assetFile'])); + $this->tplObj->addFunction(new TwigFunction('assetFile', [$this, 'assetFile'])); // Inicializa as funções personalizadas padrão foreach (Kernel::getTemplateFunctions() as $func) { - $this->tplObj->addFunction(new \Twig_SimpleFunction($func[1], $func[2])); + $this->tplObj->addFunction(new TwigFunction($func[1], $func[2])); } // Inicializa as funções personalizadas do template foreach ($this->templateFuncs as $func) { - $this->tplObj->addFunction(new \Twig_SimpleFunction($func[1], $func[2])); + $this->tplObj->addFunction(new TwigFunction($func[1], $func[2])); } return $this->tplObj->render($this->templateName . self::TPL_NAME_SUFIX, $vars); diff --git a/springy/URI.php b/springy/URI.php index 7d7cb74..ecd755b 100644 --- a/springy/URI.php +++ b/springy/URI.php @@ -10,8 +10,7 @@ namespace Springy; -use Springy\Utils\Strings_ANSI; -use Springy\Utils\Strings_UTF8; +use Springy\Utils\Strings; class URI { @@ -508,25 +507,27 @@ private static function encodeParam($query, $key, &$param) * This method sends the status header with a URI redirection to the user * browser and finish the application execution. * + * @SuppressWarnings(PHPMD.ExitExpression) + * * @param string $url the URI. * @param int $header the redirection code (default = 302). * - * @return void + * @return never */ - public static function redirect($url, $header = 302) + public static function redirect(string $url, int $header = 302): never { $redirs = [ - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 307 => 'Temporary Redirect', + 301 => ' Moved Permanently', + 302 => ' Found', + 303 => ' See Other', + 307 => ' Temporary Redirect', ]; if (ob_get_level() > 0) { ob_clean(); } - header('HTTP/1.1 ' . $header . (isset($redirs[$header]) ? $redirs[$header] : ''), true); + header('HTTP/1.1 ' . $header . ($redirs[$header] ?? ''), true); header('Status: ' . $header, true); header('Location: ' . $url, true, $header); @@ -553,11 +554,7 @@ public static function redirect($url, $header = 302) */ public static function makeSlug($txt, $space = '-', $accept = '', $lowercase = true) { - if (mb_check_encoding($txt, 'UTF-8')) { - $txt = Strings_UTF8::removeAccentedChars($txt); - } else { - $txt = Strings_ANSI::removeAccentedChars($txt); - } + $txt = Strings::removeAccentedChars($txt); if ($lowercase) { $txt = mb_strtolower(trim($txt)); @@ -586,6 +583,6 @@ public static function makeSlug($txt, $space = '-', $accept = '', $lowercase = t public static function isAjaxRequest(): bool { return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) - && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + && mb_strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; } } diff --git a/springy/Utils/JSON_Static.php b/springy/Utils/JSON_Static.php index 4327052..22c2c91 100644 --- a/springy/Utils/JSON_Static.php +++ b/springy/Utils/JSON_Static.php @@ -3,7 +3,7 @@ /** * Classe statica para tratamento JSON. * - * phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + * phpcs:disable Squiz.Classes.ValidClassName.NotPascalCase * * @copyright 2009 Lucas Cardozo * @author Lucas Cardozo diff --git a/springy/Utils/Strings.php b/springy/Utils/Strings.php index 03b15af..8d802fd 100644 --- a/springy/Utils/Strings.php +++ b/springy/Utils/Strings.php @@ -7,11 +7,13 @@ * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT * - * @version 0.14.1 + * @version 0.15.0 */ namespace Springy\Utils; +use Normalizer; + /** * Class library for string processing. */ @@ -243,13 +245,9 @@ public static function ipv4ToNumber($ipv4) /** * Troca caracteres acentuados por não acentuado. */ - public static function removeAccentedChars($txt) + public static function removeAccentedChars(string $txt): string { - if ((function_exists('mb_check_encoding') && mb_check_encoding($txt, 'UTF-8')) || self::checkUTF8($txt)) { - return Strings_UTF8::removeAccentedChars($txt); - } - - return Strings_ANSI::removeAccentedChars($txt); + return preg_replace('/[\x{0300}-\x{036F}]/u', '', Normalizer::normalize($txt, Normalizer::NFD) ?: ''); } /* As funções abaixo ainda estão em processo de migração para o framework e não devem ser utilizadas */ diff --git a/springy/Utils/Strings_ANSI.php b/springy/Utils/Strings_ANSI.php deleted file mode 100644 index 069fc62..0000000 --- a/springy/Utils/Strings_ANSI.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @author Lucas Cardozo - * - * @version 1.5.9 - */ - -namespace Springy\Utils; - -class Strings_ANSI extends Strings -{ - /** - * Troca caracteres acentuados por n�o acentuado. - */ - public static function removeAccentedChars($txt) - { - $txt = preg_replace('/[����������ê]/u', 'a', $txt); - $txt = preg_replace('/[��������]/u', 'e', $txt); - $txt = preg_replace('/[��������]/u', 'i', $txt); - $txt = preg_replace('/[����������ֺ]/u', 'o', $txt); - $txt = preg_replace('/[�������ܵ]/u', 'u', $txt); - $txt = preg_replace('/[��]/u', 'n', $txt); - $txt = preg_replace('/[��]/u', 'c', $txt); - $txt = preg_replace('/[�]/u', 'd', $txt); - $txt = preg_replace('/[��]/u', 's', $txt); - $txt = preg_replace('/[��]/u', 'y', $txt); - $txt = preg_replace('/[��]/u', 'z', $txt); - $txt = preg_replace('/[�]/u', '1', $txt); - $txt = preg_replace('/[�]/u', '2', $txt); - $txt = preg_replace('/[�]/u', '3', $txt); - $txt = preg_replace('/[��]/u', 'ae', $txt); - $txt = preg_replace('/[��]/u', '0', $txt); - $txt = preg_replace('/[�������߮�����]/u', '', $txt); - - return $txt; - } - - /** - * Converte uma string ANSI para Windows-CP-1252. - */ - public static function convertToWindowsCP1252($string) - { - $chars = ['�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�']; - $cp1252 = [chr(128), chr(146), chr(163), chr(192), chr(193), chr(194), chr(195), chr(196), chr(197), chr(198), chr(199), chr(200), chr(201), chr(202), chr(203), chr(204), chr(205), chr(206), chr(207), chr(208), chr(209), chr(210), chr(211), chr(212), chr(213), chr(214), chr(215), chr(216), chr(217), chr(218), chr(219), chr(220), chr(221), chr(222), chr(223), chr(224), chr(225), chr(226), chr(227), chr(228), chr(229), chr(230), chr(231), chr(232), chr(233), chr(234), chr(235), chr(236), chr(238), chr(239), chr(241), chr(244), chr(245), chr(251), chr(252), chr(254), chr(255)]; - - return str_replace($chars, $cp1252, $string); - } -} diff --git a/springy/Utils/Strings_UTF8.php b/springy/Utils/Strings_UTF8.php deleted file mode 100644 index b113ae4..0000000 --- a/springy/Utils/Strings_UTF8.php +++ /dev/null @@ -1,85 +0,0 @@ - * @author Fernando Val * @license https://github.com/fernandoval/Springy/blob/master/LICENSE MIT - * - * @version 0.5.3 */ namespace Springy\Validation; @@ -17,9 +15,6 @@ use Springy\Utils\MessageContainer; use Springy\Utils\Strings; -/** - * Valuation class for the user-assigned data. - */ class Validator { // Validator constants @@ -286,7 +281,7 @@ protected function explodeRules($rules) */ protected function parseMethod($rule) { - return 'validate' . str_replace(' ', '', ucwords(strtolower(str_replace('_', ' ', $rule)))); + return 'validate' . str_replace(' ', '', ucwords(mb_strtolower(str_replace('_', ' ', $rule)))); } /** diff --git a/springy/assets/errors-list.html b/springy/assets/errors-list.html deleted file mode 100644 index 3be51d8..0000000 --- a/springy/assets/errors-list.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - {systemName} v{sistemVersion} - Occurrence Errors - - - - - - -
-

System Errors

- - - - - - - - - - - - - - - - -
#ErrorDescriptionFileLineOccurrences
- -
- -
-
- - - - - diff --git a/springy/assets/system-error.html b/springy/assets/system-error.html index f1137e1..ecbf46a 100644 --- a/springy/assets/system-error.html +++ b/springy/assets/system-error.html @@ -4,7 +4,7 @@ - {systemName} v{sistemVersion} - Error {errorCode} + {systemName} v{systemVersion} - Error {errorCode} @@ -14,8 +14,8 @@

Error {errorCode}

-
+
{errorMessage}
- \ No newline at end of file + diff --git a/tests/Database/ConnectionTest.php b/tests/Database/ConnectionTest.php new file mode 100644 index 0000000..03a6990 --- /dev/null +++ b/tests/Database/ConnectionTest.php @@ -0,0 +1,76 @@ + + */ + +use PHPUnit\Framework\TestCase; +use Springy\Database\Connection; + +class ConnectionTest extends TestCase +{ + public function testConnectionMySQL() + { + $connection = new Connection('mysql'); + $this->assertTrue($connection->isConnected()); + $this->assertEquals('`test`', $connection->enclose('test')); + $this->assertEquals('*', $connection->enclose('*')); + + $sql = 'DROP TABLE IF EXISTS `test_spf`'; + + $connection->run($sql); + + $sql = 'CREATE TABLE IF NOT EXISTS `test_spf` (' + . '`id` INT NOT NULL AUTO_INCREMENT, ' + . '`name` VARCHAR(20) NULL, ' + . '`created` DATETIME NOT NULL, ' + . '`deleted` TINYINT(1) NOT NULL DEFAULT \'0\', ' + . 'PRIMARY KEY (`id`))'; + + $connection->run($sql); + $connection->run('TRUNCATE TABLE `test_spf`'); + + $result = $connection->execute( + 'INSERT INTO `test_spf`(`name`,`created`) ' + . 'VALUES (?, NOW()), (?, NOW()), (?, NOW()), (?, NOW()), (?, NOW()), (?, NOW()), (?, NOW())', + ['Homer', 'Marge', 'Lisa', 'Bart', 'Meggy', 'Santa\'\'s Helper', 'Cat'] + ); + $this->assertEquals(7, $result); + + $result = $connection->select( + 'SELECT `id`, `name` FROM `test_spf` WHERE `id` BETWEEN ? AND ? ORDER BY `id`', + [2, 5] + ); + $this->assertCount(4, $result); + $this->assertEquals(4, $connection->affectedRows()); + + $row = $connection->getFirst(); + $this->assertEquals('Marge', $row['name'] ?? null); + + $row = $connection->getNext(); + $this->assertEquals('Lisa', $row['name'] ?? null); + + $row = $connection->getPrev(); + $this->assertEquals('Marge', $row['name'] ?? null); + + $row = $connection->fetch(); + $this->assertEquals(2, $row['id'] ?? null); + + $row = $connection->getLast(); + $this->assertEquals('Meggy', $row['name'] ?? null); + + $row = $connection->getCurrent(); + $this->assertEquals(5, $row['id'] ?? null); + + $result = $connection->execute('UPDATE `test_spf` SET `name` = ? WHERE `id` = ?', ['Grampa', 6]); + $this->assertEquals(1, $result); + + $result = $connection->execute('DELETE FROM `test_spf` WHERE `id` = ?', [7]); + $this->assertEquals(1, $result); + } +} diff --git a/tests/Events/MediatorTest.php b/tests/Events/MediatorTest.php index e9946fc..b4e1351 100644 --- a/tests/Events/MediatorTest.php +++ b/tests/Events/MediatorTest.php @@ -9,8 +9,6 @@ * @copyright 2015 Fernando Val * @author Allan Marques * @author Fernando Val - * - * @version 1.0.0.5 */ use PHPUnit\Framework\TestCase; @@ -30,28 +28,19 @@ protected function setUp(): void public function testThatMediatorCanRegisterAndForgetHandlers() { - //Normal $this->mediator->registerHandlerFor('global.someevent', function () { }); $this->assertTrue($this->mediator->hasHandlersFor('global.someevent')); $this->mediator->forget('global.someevent'); $this->assertFalse($this->mediator->hasHandlersFor('global.someevent')); - - //Alternative - $this->mediator->on('global.someevent', function () { - }); - $this->assertTrue($this->mediator->hasHandlersFor('global.someevent'), 'message'); - - $this->mediator->off('global.someevent'); - $this->assertFalse($this->mediator->hasHandlersFor('global.someevent')); } public function testThatMediatorFiresASingleRegisteredEvent() { $toChange = 'not-changed'; - $this->mediator->on('global.aevent', function () use (&$toChange) { + $this->mediator->registerHandlerFor('global.aevent', function () use (&$toChange) { $toChange = 'has-changed'; }); @@ -69,7 +58,7 @@ public function testThatMediatorCanFireAnEventAndNotifySeveralHandlers() for ($i = 0; $i < 5; $i++) { $dataToChange[$i] = 'not-changed' . $i; - $this->mediator->on('global.event', function () use (&$dataToChange, $i) { + $this->mediator->registerHandlerFor('global.event', function () use (&$dataToChange, $i) { $dataToChange[$i] = 'has-changed' . $i; }); } @@ -91,17 +80,17 @@ public function testThatMediatorRespectsAPriorityOrderWhenFiringEvents() $toChangeSecond = 0; $toChangeThird = 0; - $this->mediator->on('event', function () use (&$toChangeSecond) { + $this->mediator->registerHandlerFor('event', function () use (&$toChangeSecond) { usleep(100); $toChangeSecond = microtime(true); }, 2); - $this->mediator->on('event', function () use (&$toChangeThird) { + $this->mediator->registerHandlerFor('event', function () use (&$toChangeThird) { usleep(100); $toChangeThird = microtime(true); }); - $this->mediator->on('event', function () use (&$toChangeFirst) { + $this->mediator->registerHandlerFor('event', function () use (&$toChangeFirst) { usleep(100); $toChangeFirst = microtime(true); }, 10); @@ -116,7 +105,7 @@ public function testThatMediatorPassesTheSubjectDataToTheHandlers() { $toChange = ''; - $this->mediator->on('event', function ($arg1, $arg2) use (&$toChange) { + $this->mediator->registerHandlerFor('event', function ($arg1, $arg2) use (&$toChange) { $toChange = $arg1 . $arg2; }); @@ -128,7 +117,7 @@ public function testThatMediatorPassesTheSubjectDataToTheHandlers() public function testTHatMediatorReturnsTheHandlersResponsesWhenFiring() { for ($i = 0; $i < 5; $i++) { - $this->mediator->on('event', function () use ($i) { + $this->mediator->registerHandlerFor('event', function () use ($i) { return $i; }); } @@ -145,9 +134,9 @@ public function testThatMediatorAcceptsAServiceAsAHandler() }; //default calls 'handle' method - $this->mediator->on('event', 'someService'); + $this->mediator->registerHandlerFor('event', 'someService'); //non-default calls method after '@' - $this->mediator->on('event', 'someService@doSomething'); + $this->mediator->registerHandlerFor('event', 'someService@doSomething'); $response = $this->mediator->fire('event', ['passed-', 'by-class']); @@ -156,11 +145,11 @@ public function testThatMediatorAcceptsAServiceAsAHandler() public function testThatMediatorCanFireWildCardsEventHandlers() { - $this->mediator->on('event.subevent', function () { + $this->mediator->registerHandlerFor('event.subevent', function () { return 'fromSub'; }); - $this->mediator->on('event.*', function () { + $this->mediator->registerHandlerFor('event.*', function () { return 'fromWildcard'; }); @@ -171,17 +160,17 @@ public function testThatMediatorCanFireWildCardsEventHandlers() public function testThatMediatorCanReturnTHeCurrentEvent() { - $this->mediator->on('event.subevent1', function () { + $this->mediator->registerHandlerFor('event.subevent1', function () { return 5; }); - $this->mediator->on('event.subevent2', function () { + $this->mediator->registerHandlerFor('event.subevent2', function () { return -5; }); - $this->mediator->on('event.subevent3', function () { + $this->mediator->registerHandlerFor('event.subevent3', function () { return 10; }); - $this->mediator->on('event.*', function () { + $this->mediator->registerHandlerFor('event.*', function () { switch ($this->mediator->current()) { case 'event.subevent1': return 5; @@ -206,19 +195,19 @@ public function testThatMediatorCanReturnTHeCurrentEvent() public function testThatMediatorStopsEventPropagationAfterAHandlerReturnsFalse() { - $this->mediator->on('event', function () { + $this->mediator->registerHandlerFor('event', function () { return 5; }); - $this->mediator->on('event', function () { + $this->mediator->registerHandlerFor('event', function () { return -5; }); - $this->mediator->on('event', function () { + $this->mediator->registerHandlerFor('event', function () { return 10; }); - $this->mediator->on('event', function () { + $this->mediator->registerHandlerFor('event', function () { return false; }); - $this->mediator->on('event', function () { + $this->mediator->registerHandlerFor('event', function () { return 10; }); @@ -229,7 +218,7 @@ public function testThatMediatorStopsEventPropagationAfterAHandlerReturnsFalse() public function testThatMediatorCanRegisterAHandlerForSeveralEventsAtOnce() { - $this->mediator->on(['event1', 'event2', 'event3'], function () { + $this->mediator->registerHandlerFor(['event1', 'event2', 'event3'], function () { return 5; }); diff --git a/tests/Mail/Drivers/PhpMailerTest.php b/tests/Mail/Drivers/PhpMailerTest.php new file mode 100644 index 0000000..0d2c6ed --- /dev/null +++ b/tests/Mail/Drivers/PhpMailerTest.php @@ -0,0 +1,68 @@ + + */ + +use PHPUnit\Framework\TestCase; +use Springy\Exceptions\SpringyException; +use Springy\Mail\Drivers\PhpMailer; + +class PhpMailerTest extends TestCase +{ + public $mailer; + + protected function setUp(): void + { + $this->mailer = new PhpMailer([ + 'protocol' => 'smtp', + 'host' => 'your.smtp.host.addr', + 'port' => 587, + 'cryptography' => 'tls', + 'authenticated' => false, + ]); + } + + public function testThatSendGridCanBeInstantiated() + { + $this->assertInstanceOf(PhpMailer::class, $this->mailer); + } + + public function testCallingMethods() + { + // $this->expectNotToPerformAssertions(); + $this->mailer->addAttachment(__FILE__, 'test.php', 'text/plain'); + $this->mailer->addBcc('to.bcc@example.com', 'Secret Name'); + $this->mailer->addCc('to.copy@example.com', 'Copy Name'); + $this->mailer->addHeader('Errors-To', 'errors.to@example.com'); + $this->mailer->addTo('to.mail@example.com', 'To Name'); + $this->mailer->setAlternativeBody('Alternative body'); + $this->mailer->setBody('

HTML body

', true); + $this->mailer->setFrom('send.from@example.com', 'Sender Name'); + $this->mailer->setSubject('Test PHP Mailer'); + $this->assertIsString($this->mailer->getLastError()); + } + + public function testUnsupportedCategory() + { + $this->expectException(SpringyException::class); + $this->mailer->addCategory('test'); + } + + public function testUnsupportedTemplateVar() + { + $this->expectException(SpringyException::class); + $this->mailer->addTemplateVar('var', 'value'); + } + + public function testUnsupportedTemplateId() + { + $this->expectException(SpringyException::class); + $this->mailer->setTemplateId('id_of_template'); + } +} diff --git a/tests/Mail/Drivers/SendGridTest.php b/tests/Mail/Drivers/SendGridTest.php new file mode 100644 index 0000000..022266e --- /dev/null +++ b/tests/Mail/Drivers/SendGridTest.php @@ -0,0 +1,53 @@ + + */ + +use PHPUnit\Framework\TestCase; +use Springy\Mail\Drivers\SendGrid; + +class SendGridTest extends TestCase +{ + public $sendgrid; + + protected function setUp(): void + { + $this->sendgrid = new SendGrid([ + 'apikey' => 'your_sendgrid_api_key', + 'options' => [ + 'protocol' => 'https', + 'raise_exceptions' => false, + 'turn_off_ssl_verification' => false, + ], + ]); + } + + public function testThatSendGridCanBeInstantiated() + { + $this->assertInstanceOf(SendGrid::class, $this->sendgrid); + } + + public function testCallingMethods() + { + // $this->expectNotToPerformAssertions(); + $this->sendgrid->addAttachment(__FILE__, 'test.php', 'text/plain'); + $this->sendgrid->addBcc('to.bcc@example.com', 'Secret Name'); + $this->sendgrid->addCategory('test'); + $this->sendgrid->addCc('to.copy@example.com', 'Copy Name'); + $this->sendgrid->addHeader('Errors-To', 'errors.to@example.com'); + $this->sendgrid->addTemplateVar('var', 'value'); + $this->sendgrid->addTo('to.mail@example.com', 'To Name'); + $this->sendgrid->setAlternativeBody('Alternative body'); + $this->sendgrid->setBody('

HTML body

', true); + $this->sendgrid->setFrom('send.from@example.com', 'Sender Name'); + $this->sendgrid->setSubject('Test SendGrid'); + $this->sendgrid->setTemplateId('id_of_template'); + $this->assertIsString($this->sendgrid->getLastError()); + } +} diff --git a/tests/Mail/MailerTest.php b/tests/Mail/MailerTest.php new file mode 100644 index 0000000..0f2bc3f --- /dev/null +++ b/tests/Mail/MailerTest.php @@ -0,0 +1,47 @@ + + */ + +use PHPUnit\Framework\TestCase; +use Springy\Mail\Mailer; + +class MailerTest extends TestCase +{ + public $mailer; + + protected function setUp(): void + { + $this->mailer = new Mailer(); + } + + public function testThatSendGridCanBeInstantiated() + { + $this->assertInstanceOf(Mailer::class, $this->mailer); + } + + public function testCallingMethods() + { + // $this->expectNotToPerformAssertions(); + $this->mailer + ->addAttachment(__FILE__, 'test.php', 'text/plain') + ->addBcc('to.bcc@example.com', 'Secret Name') + ->addCategory('test') + ->addCc('to.copy@example.com', 'Copy Name') + ->addHeader('Errors-To', 'errors.to@example.com') + ->addTemplateVar('var', 'value') + ->addTo('to.mail@example.com', 'To Name') + ->setAlternativeBody('Alternative body') + ->setBody('

HTML body

', true) + ->setFrom('send.from@example.com', 'Sender Name') + ->setSubject('Test Mailer') + ->setTemplateId('id_of_template'); + $this->assertIsString($this->mailer->getLastError()); + } +} diff --git a/tests/Utils/StringsTest.php b/tests/Utils/StringsTest.php index 5db119e..91e6dfe 100644 --- a/tests/Utils/StringsTest.php +++ b/tests/Utils/StringsTest.php @@ -9,7 +9,7 @@ * @author Allan Marques * @author Fernando Val * - * @version 1.0.0.3 + * @version 1.0.4 */ use PHPUnit\Framework\TestCase; @@ -28,6 +28,11 @@ public function testEmailGetsValidateSuccessfully() $this->assertTrue(Strings::validateEmailAddress('fernando@fval.nonexiuuste', false)); } + public function testRemoveAccentedChars() + { + $this->assertEquals('aeiou', Strings::removeAccentedChars('áéíóú')); + } + public function testCnpj() { $cnpj = '22.608.842/0001-63'; diff --git a/tests/Utils/UUIDTest.php b/tests/Utils/UUIDTest.php deleted file mode 100644 index 75474cf..0000000 --- a/tests/Utils/UUIDTest.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * @version 1.0.0.2 - */ - -use PHPUnit\Framework\TestCase; -use Springy\Utils\UUID; - -class UUIDTest extends TestCase -{ - public function testRandomUUID() - { - $uuid1 = UUID::random(); - $uuid2 = UUID::random(); - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $uuid1); - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $uuid2); - $this->assertNotEquals($uuid1, $uuid2); - } - - public function testV3UUID() - { - $name = 'test'; - $uuid = UUID::random(); - $v3_1 = UUID::v3($uuid, $name); - $v3_2 = UUID::v3($uuid, $name); - - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $v3_1); - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $v3_2); - $this->assertEquals($v3_1, $v3_2); - } - - public function testV4UUID() - { - $uuid1 = UUID::v4(); - $uuid2 = UUID::v4(); - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $uuid1); - $this->assertMatchesRegularExpression('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $uuid2); - $this->assertNotEquals($uuid1, $uuid2); - } -} diff --git a/tests/bin/Dockerfile b/tests/bin/Dockerfile new file mode 100644 index 0000000..eb19ce7 --- /dev/null +++ b/tests/bin/Dockerfile @@ -0,0 +1,12 @@ +ARG PHP_VERSION=8.2 +FROM php:${PHP_VERSION}-cli +RUN apt-get update && apt-get install -y git curl unzip libgmp-dev libicu-dev libonig-dev libpng-dev libxml2-dev \ + && docker-php-ext-configure intl \ + && docker-php-ext-install gmp intl mbstring pdo pdo_mysql \ + && docker-php-ext-enable gmp intl mbstring \ + && apt-get clean && rm -rf /var/lib/apt/lists/* +COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer +COPY . /usr/src/springy +WORKDIR /usr/src/springy +RUN composer install --optimize-autoloader +CMD [ "composer", "run", "test" ] \ No newline at end of file diff --git a/tests/bin/run b/tests/bin/run index 812fe67..9948e81 100644 --- a/tests/bin/run +++ b/tests/bin/run @@ -3,37 +3,101 @@ # Command line script helper to run PHPUnit tests on supported PHP versions # using Docker containers. # +# Usin act project: +# +# ~/act --matrix php-version:8.5 +# # Copyright © Fernando Val # SCRIPT=$(readlink -f "$0") SCRIPTPATH=$(dirname "$SCRIPT") +VERSIONS=${@:-8.1 8.2 8.3 8.4 8.5} # Colors Color_Off='\033[0m' Blue='\033[0;34m' Green='\033[0;32m' -Yellow='\033[1;33m' +Red='\033[0;31m' White='\033[0;37m' BWhite='\033[1;37m' +Yellow='\033[1;33m' set -e -cd $SCRIPTPATH/../.. +cd $SCRIPTPATH -echo "" -echo -e "${Green}Running PHPUnit tests on PHP 8.1${Color_Off}" -echo -e "${Green}--------------------------------${Color_Off}" -docker run --rm -v $(pwd):/var/www -w /var/www php:8.1 vendor/bin/phpunit -echo "" +# Names with timestamp to avoid conflicts +NET_NAME="rede-teste-$(date +%s)" +DB_HOST="mysql-teste-$(date +%s)" +DB_NAME=springy +DB_USER=root +DB_PASS=root -echo "" -echo -e "${Green}Running PHPUnit tests on PHP 8.2${Color_Off}" -echo -e "${Green}--------------------------------${Color_Off}" -docker run --rm -v $(pwd):/var/www -w /var/www php:8.2 vendor/bin/phpunit -echo "" +# Cleanup function to stop and remove the MySQL container and network +cleanup() { + docker stop "$DB_HOST" > /dev/null 2>&1 + docker network rm "$NET_NAME" > /dev/null 2>&1 + echo -e "${Green}Network and Container removed.${Color_Off}" +} +echo -e "${BWhite} __ _ ${Color_Off}" +echo -e "${BWhite}/ _\_ __ _ __(_)_ __ __ _ _ _ ${Color_Off}" +echo -e "${BWhite}\ \| '_ \| '__| | '_ \ / _\` | | | |${Color_Off}" +echo -e "${BWhite}_\ \ |_) | | | | | | | (_| | |_| |${Color_Off}" +echo -e "${BWhite}\__/ .__/|_| |_|_| |_|\__, |\__, |${Color_Off}" +echo -e "${BWhite} |_| |___/ |___/ ${Color_Off} Test Suite" echo "" -echo -e "${Green}Running PHPUnit tests on PHP 8.3${Color_Off}" -echo -e "${Green}--------------------------------${Color_Off}" -docker run --rm -v $(pwd):/var/www -w /var/www php:8.3 vendor/bin/phpunit -echo "" + +# Ensure the MySQL container is stopped and removed on script exit +trap "cleanup" EXIT + +# Create a Docker network for the MySQL container +echo -e "${Yellow}Creating test network...${Color_Off}" +docker network create $NET_NAME > /dev/null + +# Start MySQL container for testing +echo -e "${Yellow}Starting MySQL...${Color_Off}" +docker run --name "$DB_HOST" \ + -e MYSQL_DATABASE=$DB_NAME \ + -e MYSQL_ROOT_PASSWORD=$DB_PASS \ + -p 3306:3306 \ + --network $NET_NAME \ + --rm -d mysql:8.0 > /dev/null + +echo -e "${Yellow}Waiting for MySQL...${Color_Off}" +TIMEOUT=30 +COUNTER=0 +until docker exec "$DB_HOST" mysqladmin ping -h localhost --silent; do + sleep 1 + COUNTER=$((COUNTER + 1)) + if [ $COUNTER -ge $TIMEOUT ]; then + echo -e "${Red}Error: MySQL does not start in time.${Color_Off}" + exit 1 # The 'trap' will be triggered here and kill the container + fi +done + +echo -e "${Green}MySQL ready! Running tests...${Color_Off}" + +# Loop through each specified PHP version, build the Docker image, and run tests +for version in $VERSIONS; do + IMAGE_NAME="springy-test:$version" + + # Build the image first + echo -e "${Yellow}Building PHP $version image...${Color_Off}" + docker build -q -f $SCRIPTPATH/Dockerfile \ + -t "$IMAGE_NAME" \ + --build-arg PHP_VERSION="$version" \ + $SCRIPTPATH/../.. > /dev/null + + # Run tests in the container + echo -e "${Yellow}Running tests on PHP $version...${Color_Off}" + docker run -it --rm \ + --network $NET_NAME \ + -e DB_HOST=$DB_HOST \ + -e DB_NAME=$DB_NAME \ + -e DB_USER=$DB_USER \ + -e DB_PASS=$DB_PASS \ + "$IMAGE_NAME" +done + +echo -e "${Green}All tests completed!${Color_Off}" \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5554b19..4e5f73f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,12 +6,12 @@ * @copyright 2015 Fernando Val * @author Fernando Val * - * @version 1.1.0 + * @version 1.2.0 */ define('SPRINGY_START', microtime(true)); -require __DIR__ . '/../consts'; +require __DIR__ . '/../consts.php'; // Loads the Composer autoload require __DIR__ . '/../vendor/autoload.php'; diff --git a/www/_Main.php b/www/_Main.php index a59437b..5188c13 100644 --- a/www/_Main.php +++ b/www/_Main.php @@ -6,20 +6,15 @@ * @copyright 2007 Fernando Val * @author Fernando Val * - * @version 6.0.0 - * - * @codingStandardsIgnoreFile + * @version 6.1.0 */ define('SPRINGY_START', microtime(true)); -require __DIR__ . '/../consts'; +require __DIR__ . '/../consts.php'; // Loads the Composer autoload require __DIR__ . '/../vendor/autoload.php'; -// Load framework configuration -$sysconf = file_exists('sysconf.php') ? require_once 'sysconf.php' : []; - // Define error handlers error_reporting(E_ALL); set_exception_handler('springyExceptionHandler'); @@ -27,7 +22,7 @@ // System start ob_start(); -Springy\Kernel::run($sysconf); +Springy\Kernel::run(); if (count(ob_list_handlers())) { ob_end_flush(); diff --git a/www/sysconf.php b/www/sysconf.php deleted file mode 100644 index c1b3d73..0000000 --- a/www/sysconf.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * @deprecated 4.6.0 - * - * @see .env.example and const files at the project root. - * - * @version 3.6.0 - */ - -$baseDir = dirname(__DIR__); - -return [ - 'SYSTEM_NAME' => 'Your system name', - 'SYSTEM_VERSION' => [1, 0, 0], - 'PROJECT_CODE_NAME' => '', - - // Project root directory - 'PROJECT_PATH' => $baseDir, - // Configuration directory - 'CONFIG_PATH' => $baseDir . DIRECTORY_SEPARATOR . 'conf', - // Application directory - 'APP_PATH' => $baseDir . DIRECTORY_SEPARATOR . 'app', - // Directory where the system writes data during the course of its operation - 'VAR_PATH' => $baseDir . DIRECTORY_SEPARATOR . 'var', - // Directory for the subdirectories with migration scripts - 'MIGRATION_PATH' => $baseDir . DIRECTORY_SEPARATOR . 'migration', -];