From 14849191b7f99cf0a76dac8062472d98b80a2166 Mon Sep 17 00:00:00 2001 From: Nikita Loges Date: Sun, 3 Dec 2023 13:17:29 +0100 Subject: [PATCH] update code --- .github/workflows/php.yml | 7 +- .gitignore | 2 +- README.md | 52 +- composer-setup.php | 5092 ----------------- composer-unused.php | 12 + composer.json | 37 +- phpcs.xml | 1 - phpstan.neon.dist | 6 - phpunit.xml.dist | 30 +- .../Compiler/EventHandlerCompiler.php | 8 +- .../ShapecodeTwigTemplateEventExtension.php | 9 +- src/Event/Code/TwigEventCode.php | 9 +- src/Event/Code/TwigEventInclude.php | 25 +- src/Event/Code/TwigEventRender.php | 41 +- src/Event/Code/TwigEventString.php | 25 +- src/Event/Handler/HandlerInterface.php | 4 +- src/Event/Handler/IncludeHandler.php | 13 +- src/Event/Handler/RenderHandler.php | 18 +- src/Event/Handler/StringHandler.php | 10 +- src/Event/TwigTemplateEvent.php | 48 +- src/Manager/HandlerManager.php | 12 +- src/Manager/HandlerManagerInterface.php | 14 - src/Resources/config/services.yml | 17 +- src/Services/EventService.php | 43 +- src/Services/EventServiceInterface.php | 16 - src/ShapecodeTwigTemplateEventBundle.php | 4 +- src/Twig/EventExtension.php | 15 +- tests/Services/EventServiceTest.php | 8 +- 28 files changed, 161 insertions(+), 5417 deletions(-) delete mode 100644 composer-setup.php create mode 100644 composer-unused.php delete mode 100644 src/Manager/HandlerManagerInterface.php delete mode 100644 src/Services/EventServiceInterface.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 24acf7c..a80ff1c 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,11 +15,16 @@ jobs: strategy: matrix: - php-versions: ['7.2', '7.3', '7.4', '8.0'] + php-versions: ['8.2', '8.3'] steps: - uses: actions/checkout@v2 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + - name: Validate composer.json and composer.lock run: composer validate diff --git a/.gitignore b/.gitignore index f137394..7b401ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ +.phpunit.cache/ composer.lock -composer.phar vendor/ diff --git a/README.md b/README.md index fa39cc1..0468fb4 100644 --- a/README.md +++ b/README.md @@ -18,69 +18,57 @@ Install instructions -------------------------------- First you need to add `shapecode/twig-template-event-bundle` to `composer.json`: - -``` json +```bash +composer require shapecode/twig-template-event-bundle +``` +... or ... +```json { "require": { - "shapecode/twig-template-event-bundle": "~3.0" + "shapecode/twig-template-event-bundle": "~5.0" } } ``` -Please note that `dev-master` points to the latest release. If you want to use the latest development version please use `dev-develop`. Of course you can also use an explicit version number, e.g., `1.0.*`. +If you dont use Symfony Flex you have to add `ShapecodeTwigTemplateEventBundle` to your `bundles.php`: -You have to add `ShapecodeTwigTemplateEventBundle` to your `AppKernel.php`: - -``` php +```php ['all' => true], +]; ``` Now you can set events in twig templates: -``` twig +```twig {{ event('test') }} ``` And listen to them with an event listener: -``` -// services.yml +```yaml services: # twig events - Shapecode\Bundle\TwigTemplateEventBundle\EventListener\TestTwigEventListener: - tags: - - { name: kernel.event_listener, event: shapecode.twig_template.event, method: onTemplateEvent } + Shapecode\Bundle\TwigTemplateEventBundle\EventListener\TestTwigEventListener: ~ ``` -``` php +```php getEventName() == 'test') { $event->addCode(new TwigEventString('hello {{ world }}', array( diff --git a/composer-setup.php b/composer-setup.php deleted file mode 100644 index d80a8b8..0000000 --- a/composer-setup.php +++ /dev/null @@ -1,5092 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -setupEnvironment(); -process(is_array($argv) ? $argv : array()); - -/** - * Initializes various values - * - * @throws RuntimeException If uopz extension prevents exit calls - */ -function setupEnvironment() -{ - ini_set('display_errors', 1); - - if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) { - // uopz works at opcode level and disables exit calls - if (function_exists('uopz_allow_exit')) { - @uopz_allow_exit(true); - } else { - throw new RuntimeException('The uopz extension ignores exit calls and breaks this installer.'); - } - } - - $installer = 'Composer Installer'; - - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { - if ($version = getenv('COMPOSERSETUP')) { - $installer = sprintf('Composer-Setup.exe %s', $version); - } - } - - define('COMPOSER_INSTALLER', $installer); -} - -/** - * Processes the installer - */ -function process($argv) -{ - // Determine ANSI output from --ansi and --no-ansi flags - setUseAnsi($argv); - - if (in_array('--help', $argv)) { - displayHelp(); - exit(0); - } - - $check = in_array('--check', $argv); - $help = in_array('--help', $argv); - $force = in_array('--force', $argv); - $quiet = in_array('--quiet', $argv); - $channel = 'stable'; - if (in_array('--snapshot', $argv)) { - $channel = 'snapshot'; - } elseif (in_array('--preview', $argv)) { - $channel = 'preview'; - } elseif (in_array('--1', $argv)) { - $channel = '1'; - } elseif (in_array('--2', $argv)) { - $channel = '2'; - } - $disableTls = in_array('--disable-tls', $argv); - $installDir = getOptValue('--install-dir', $argv, false); - $version = getOptValue('--version', $argv, false); - $filename = getOptValue('--filename', $argv, 'composer.phar'); - $cafile = getOptValue('--cafile', $argv, false); - - if (!checkParams($installDir, $version, $cafile)) { - exit(1); - } - - $ok = checkPlatform($warnings, $quiet, $disableTls, true); - - if ($check) { - // Only show warnings if we haven't output any errors - if ($ok) { - showWarnings($warnings); - showSecurityWarning($disableTls); - } - exit($ok ? 0 : 1); - } - - if ($ok || $force) { - $installer = new Installer($quiet, $disableTls, $cafile); - if ($installer->run($version, $installDir, $filename, $channel)) { - showWarnings($warnings); - showSecurityWarning($disableTls); - exit(0); - } - } - - exit(1); -} - -/** - * Displays the help - */ -function displayHelp() -{ - echo << $value) { - $next = $key + 1; - if (0 === strpos($value, $opt)) { - if ($optLength === strlen($value) && isset($argv[$next])) { - return trim($argv[$next]); - } else { - return trim(substr($value, $optLength + 1)); - } - } - } - - return $default; -} - -/** - * Checks that user-supplied params are valid - * - * @param mixed $installDir The required istallation directory - * @param mixed $version The required composer version to install - * @param mixed $cafile Certificate Authority file - * - * @return bool True if the supplied params are okay - */ -function checkParams($installDir, $version, $cafile) -{ - $result = true; - - if (false !== $installDir && !is_dir($installDir)) { - out("The defined install dir ({$installDir}) does not exist.", 'info'); - $result = false; - } - - if (false !== $version && 1 !== preg_match('/^\d+\.\d+\.\d+(\-(alpha|beta|RC)\d*)*$/', $version)) { - out("The defined install version ({$version}) does not match release pattern.", 'info'); - $result = false; - } - - if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) { - out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info'); - $result = false; - } - return $result; -} - -/** - * Checks the platform for possible issues running Composer - * - * Errors are written to the output, warnings are saved for later display. - * - * @param array $warnings Populated by method, to be shown later - * @param bool $quiet Quiet mode - * @param bool $disableTls Bypass tls - * @param bool $install If we are installing, rather than diagnosing - * - * @return bool True if there are no errors - */ -function checkPlatform(&$warnings, $quiet, $disableTls, $install) -{ - getPlatformIssues($errors, $warnings, $install); - - // Make openssl warning an error if tls has not been specifically disabled - if (isset($warnings['openssl']) && !$disableTls) { - $errors['openssl'] = $warnings['openssl']; - unset($warnings['openssl']); - } - - if (!empty($errors)) { - out('Some settings on your machine make Composer unable to work properly.', 'error'); - out('Make sure that you fix the issues listed below and run this script again:', 'error'); - outputIssues($errors); - return false; - } - - if (empty($warnings) && !$quiet) { - out('All settings correct for using Composer', 'success'); - } - return true; -} - -/** - * Checks platform configuration for common incompatibility issues - * - * @param array $errors Populated by method - * @param array $warnings Populated by method - * @param bool $install If we are installing, rather than diagnosing - * - * @return bool If any errors or warnings have been found - */ -function getPlatformIssues(&$errors, &$warnings, $install) -{ - $errors = array(); - $warnings = array(); - - if ($iniPath = php_ini_loaded_file()) { - $iniMessage = PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath; - } else { - $iniMessage = PHP_EOL.'A php.ini file does not exist. You will have to create one.'; - } - $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; - - if (ini_get('detect_unicode')) { - $errors['unicode'] = array( - 'The detect_unicode setting must be disabled.', - 'Add the following to the end of your `php.ini`:', - ' detect_unicode = Off', - $iniMessage - ); - } - - if (extension_loaded('suhosin')) { - $suhosin = ini_get('suhosin.executor.include.whitelist'); - $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist'); - if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) { - $errors['suhosin'] = array( - 'The suhosin.executor.include.whitelist setting is incorrect.', - 'Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):', - ' suhosin.executor.include.whitelist = phar '.$suhosin, - $iniMessage - ); - } - } - - if (!function_exists('json_decode')) { - $errors['json'] = array( - 'The json extension is missing.', - 'Install it or recompile php without --disable-json' - ); - } - - if (!extension_loaded('Phar')) { - $errors['phar'] = array( - 'The phar extension is missing.', - 'Install it or recompile php without --disable-phar' - ); - } - - if (!extension_loaded('filter')) { - $errors['filter'] = array( - 'The filter extension is missing.', - 'Install it or recompile php without --disable-filter' - ); - } - - if (!extension_loaded('hash')) { - $errors['hash'] = array( - 'The hash extension is missing.', - 'Install it or recompile php without --disable-hash' - ); - } - - if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { - $errors['iconv_mbstring'] = array( - 'The iconv OR mbstring extension is required and both are missing.', - 'Install either of them or recompile php without --disable-iconv' - ); - } - - if (!ini_get('allow_url_fopen')) { - $errors['allow_url_fopen'] = array( - 'The allow_url_fopen setting is incorrect.', - 'Add the following to the end of your `php.ini`:', - ' allow_url_fopen = On', - $iniMessage - ); - } - - if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { - $ioncube = ioncube_loader_version(); - $errors['ioncube'] = array( - 'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.', - 'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:', - ' zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so', - $iniMessage - ); - } - - if (version_compare(PHP_VERSION, '5.3.2', '<')) { - $errors['php'] = array( - 'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.' - ); - } - - if (version_compare(PHP_VERSION, '5.3.4', '<')) { - $warnings['php'] = array( - 'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.', - 'Composer works with 5.3.2+ for most people, but there might be edge case issues.' - ); - } - - if (!extension_loaded('openssl')) { - $warnings['openssl'] = array( - 'The openssl extension is missing, which means that secure HTTPS transfers are impossible.', - 'If possible you should enable it or recompile php with --with-openssl' - ); - } - - if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { - // Attempt to parse version number out, fallback to whole string value. - $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' ')); - $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' ')); - $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT; - - $warnings['openssl_version'] = array( - 'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.', - 'If possible you should upgrade OpenSSL to version 1.0.1 or above.' - ); - } - - if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { - $warnings['apc_cli'] = array( - 'The apc.enable_cli setting is incorrect.', - 'Add the following to the end of your `php.ini`:', - ' apc.enable_cli = Off', - $iniMessage - ); - } - - if (!$install && extension_loaded('xdebug')) { - $warnings['xdebug_loaded'] = array( - 'The xdebug extension is loaded, this can slow down Composer a little.', - 'Disabling it when using Composer is recommended.' - ); - - if (ini_get('xdebug.profiler_enabled')) { - $warnings['xdebug_profile'] = array( - 'The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.', - 'Add the following to the end of your `php.ini` to disable it:', - ' xdebug.profiler_enabled = 0', - $iniMessage - ); - } - } - - if (!extension_loaded('zlib')) { - $warnings['zlib'] = array( - 'The zlib extension is not loaded, this can slow down Composer a lot.', - 'If possible, install it or recompile php with --with-zlib', - $iniMessage - ); - } - - if (defined('PHP_WINDOWS_VERSION_BUILD') - && (version_compare(PHP_VERSION, '7.2.23', '<') - || (version_compare(PHP_VERSION, '7.3.0', '>=') - && version_compare(PHP_VERSION, '7.3.10', '<')))) { - $warnings['onedrive'] = array( - 'The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.', - 'Upgrade your PHP ('.PHP_VERSION.') to use this location with Composer.' - ); - } - - if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) { - $warnings['uopz'] = array( - 'The uopz extension ignores exit calls and may not work with all Composer commands.', - 'Disabling it when using Composer is recommended.' - ); - } - - ob_start(); - phpinfo(INFO_GENERAL); - $phpinfo = ob_get_clean(); - if (preg_match('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { - $configure = $match[1]; - - if (false !== strpos($configure, '--enable-sigchild')) { - $warnings['sigchild'] = array( - 'PHP was compiled with --enable-sigchild which can cause issues on some platforms.', - 'Recompile it without this flag if possible, see also:', - ' https://bugs.php.net/bug.php?id=22999' - ); - } - - if (false !== strpos($configure, '--with-curlwrappers')) { - $warnings['curlwrappers'] = array( - 'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.', - 'Recompile it without this flag if possible' - ); - } - } - - // Stringify the message arrays - foreach ($errors as $key => $value) { - $errors[$key] = PHP_EOL.implode(PHP_EOL, $value); - } - - foreach ($warnings as $key => $value) { - $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value); - } - - return !empty($errors) || !empty($warnings); -} - - -/** - * Outputs an array of issues - * - * @param array $issues - */ -function outputIssues($issues) -{ - foreach ($issues as $issue) { - out($issue, 'info'); - } - out(''); -} - -/** - * Outputs any warnings found - * - * @param array $warnings - */ -function showWarnings($warnings) -{ - if (!empty($warnings)) { - out('Some settings on your machine may cause stability issues with Composer.', 'error'); - out('If you encounter issues, try to change the following:', 'error'); - outputIssues($warnings); - } -} - -/** - * Outputs an end of process warning if tls has been bypassed - * - * @param bool $disableTls Bypass tls - */ -function showSecurityWarning($disableTls) -{ - if ($disableTls) { - out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info'); - out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info'); - } -} - -/** - * colorize output - */ -function out($text, $color = null, $newLine = true) -{ - $styles = array( - 'success' => "\033[0;32m%s\033[0m", - 'error' => "\033[31;31m%s\033[0m", - 'info' => "\033[33;33m%s\033[0m" - ); - - $format = '%s'; - - if (isset($styles[$color]) && USE_ANSI) { - $format = $styles[$color]; - } - - if ($newLine) { - $format .= PHP_EOL; - } - - printf($format, $text); -} - -/** - * Returns the system-dependent Composer home location, which may not exist - * - * @return string - */ -function getHomeDir() -{ - $home = getenv('COMPOSER_HOME'); - - if (!$home) { - $userDir = getUserDir(); - - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { - $home = $userDir.'/Composer'; - } else { - $home = $userDir.'/.composer'; - - if (!is_dir($home) && useXdg()) { - // XDG Base Directory Specifications - if (!($xdgConfig = getenv('XDG_CONFIG_HOME'))) { - $xdgConfig = $userDir.'/.config'; - } - $home = $xdgConfig.'/composer'; - } - } - } - return $home; -} - -/** - * Returns the location of the user directory from the environment - * @throws RuntimeException If the environment value does not exists - * - * @return string - */ -function getUserDir() -{ - $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME'; - $userDir = getenv($userEnv); - - if (!$userDir) { - throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly'); - } - - return rtrim(strtr($userDir, '\\', '/'), '/'); -} - -/** - * @return bool - */ -function useXdg() -{ - foreach (array_keys($_SERVER) as $key) { - if (substr($key, 0, 4) === 'XDG_') { - return true; - } - } - return false; -} - -function validateCaFile($contents) -{ - // assume the CA is valid if php is vulnerable to - // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html - if ( - PHP_VERSION_ID <= 50327 - || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422) - || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506) - ) { - return !empty($contents); - } - - return (bool) openssl_x509_parse($contents); -} - -class Installer -{ - private $quiet; - private $disableTls; - private $cafile; - private $installPath; - private $target; - private $tmpFile; - private $baseUrl; - private $algo; - private $errHandler; - private $httpClient; - private $pubKeys = array(); - private $installs = array(); - - /** - * Constructor - must not do anything that throws an exception - * - * @param bool $quiet Quiet mode - * @param bool $disableTls Bypass tls - * @param mixed $cafile Path to CA bundle, or false - */ - public function __construct($quiet, $disableTls, $caFile) - { - if (($this->quiet = $quiet)) { - ob_start(); - } - $this->disableTls = $disableTls; - $this->cafile = $caFile; - $this->errHandler = new ErrorHandler(); - } - - /** - * Runs the installer - * - * @param mixed $version Specific version to install, or false - * @param mixed $installDir Specific installation directory, or false - * @param string $filename Specific filename to save to, or composer.phar - * @param string $channel Specific version channel to use - * @throws Exception If anything other than a RuntimeException is caught - * - * @return bool If the installation succeeded - */ - public function run($version, $installDir, $filename, $channel) - { - try { - $this->initTargets($installDir, $filename); - $this->initTls(); - $this->httpClient = new HttpClient($this->disableTls, $this->cafile); - $result = $this->install($version, $channel); - - // in case --1 or --2 is passed, we leave the default channel for next self-update to stable - if (is_numeric($channel)) { - $channel = 'stable'; - } - - if ($result && $channel !== 'stable' && !$version && defined('PHP_BINARY')) { - $null = (defined('PHP_WINDOWS_VERSION_MAJOR') ? 'NUL' : '/dev/null'); - @exec(escapeshellarg(PHP_BINARY) .' '.escapeshellarg($this->target).' self-update --'.$channel.' --set-channel-only -q > '.$null.' 2> '.$null, $output); - } - } catch (Exception $e) { - $result = false; - } - - // Always clean up - $this->cleanUp($result); - - if (isset($e)) { - // Rethrow anything that is not a RuntimeException - if (!$e instanceof RuntimeException) { - throw $e; - } - out($e->getMessage(), 'error'); - } - return $result; - } - - /** - * Initialization methods to set the required filenames and composer url - * - * @param mixed $installDir Specific installation directory, or false - * @param string $filename Specific filename to save to, or composer.phar - * @throws RuntimeException If the installation directory is not writable - */ - protected function initTargets($installDir, $filename) - { - $this->installPath = (is_dir($installDir) ? rtrim($installDir, '/').'/' : '').$filename; - $installDir = realpath($installDir) ? realpath($installDir) : getcwd(); - - if (!is_writeable($installDir)) { - throw new RuntimeException('The installation directory "'.$installDir.'" is not writable'); - } - - $this->target = $installDir.DIRECTORY_SEPARATOR.$filename; - $this->tmpFile = $installDir.DIRECTORY_SEPARATOR.basename($this->target, '.phar').'-temp.phar'; - - $uriScheme = $this->disableTls ? 'http' : 'https'; - $this->baseUrl = $uriScheme.'://getcomposer.org'; - } - - /** - * A wrapper around methods to check tls and write public keys - * @throws RuntimeException If SHA384 is not supported - */ - protected function initTls() - { - if ($this->disableTls) { - return; - } - - if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) { - throw new RuntimeException('SHA384 is not supported by your openssl extension'); - } - - $this->algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384'; - $home = $this->getComposerHome(); - - $this->pubKeys = array( - 'dev' => $this->installKey(self::getPKDev(), $home, 'keys.dev.pub'), - 'tags' => $this->installKey(self::getPKTags(), $home, 'keys.tags.pub') - ); - - if (empty($this->cafile) && !HttpClient::getSystemCaRootBundlePath()) { - $this->cafile = $this->installKey(HttpClient::getPackagedCaFile(), $home, 'cacert.pem'); - } - } - - /** - * Returns the Composer home directory, creating it if required - * @throws RuntimeException If the directory cannot be created - * - * @return string - */ - protected function getComposerHome() - { - $home = getHomeDir(); - - if (!is_dir($home)) { - $this->errHandler->start(); - - if (!mkdir($home, 0777, true)) { - throw new RuntimeException(sprintf( - 'Unable to create Composer home directory "%s": %s', - $home, - $this->errHandler->message - )); - } - $this->installs[] = $home; - $this->errHandler->stop(); - } - return $home; - } - - /** - * Writes public key data to disc - * - * @param string $data The public key(s) in pem format - * @param string $path The directory to write to - * @param string $filename The name of the file - * @throws RuntimeException If the file cannot be written - * - * @return string The path to the saved data - */ - protected function installKey($data, $path, $filename) - { - $this->errHandler->start(); - - $target = $path.DIRECTORY_SEPARATOR.$filename; - $installed = file_exists($target); - $write = file_put_contents($target, $data, LOCK_EX); - @chmod($target, 0644); - - $this->errHandler->stop(); - - if (!$write) { - throw new RuntimeException(sprintf('Unable to write %s to: %s', $filename, $path)); - } - - if (!$installed) { - $this->installs[] = $target; - } - - return $target; - } - - /** - * The main install function - * - * @param mixed $version Specific version to install, or false - * @param string $channel Version channel to use - * - * @return bool If the installation succeeded - */ - protected function install($version, $channel) - { - $retries = 3; - $result = false; - $infoMsg = 'Downloading...'; - $infoType = 'info'; - - while ($retries--) { - if (!$this->quiet) { - out($infoMsg, $infoType); - $infoMsg = 'Retrying...'; - $infoType = 'error'; - } - - if (!$this->getVersion($channel, $version, $url, $error)) { - out($error, 'error'); - continue; - } - - if (!$this->downloadToTmp($url, $signature, $error)) { - out($error, 'error'); - continue; - } - - if (!$this->verifyAndSave($version, $signature, $error)) { - out($error, 'error'); - continue; - } - - $result = true; - break; - } - - if (!$this->quiet) { - if ($result) { - out(PHP_EOL."Composer (version {$version}) successfully installed to: {$this->target}", 'success'); - out("Use it: php {$this->installPath}", 'info'); - out(''); - } else { - out('The download failed repeatedly, aborting.', 'error'); - } - } - return $result; - } - - /** - * Sets the version url, downloading version data if required - * - * @param string $channel Version channel to use - * @param false|string $version Version to install, or set by method - * @param null|string $url The versioned url, set by method - * @param null|string $error Set by method on failure - * - * @return bool If the operation succeeded - */ - protected function getVersion($channel, &$version, &$url, &$error) - { - $error = ''; - - if ($version) { - if (empty($url)) { - $url = $this->baseUrl."/download/{$version}/composer.phar"; - } - return true; - } - - $this->errHandler->start(); - - if ($this->downloadVersionData($data, $error)) { - $this->parseVersionData($data, $channel, $version, $url); - } - - $this->errHandler->stop(); - return empty($error); - } - - /** - * Downloads and json-decodes version data - * - * @param null|array $data Downloaded version data, set by method - * @param null|string $error Set by method on failure - * - * @return bool If the operation succeeded - */ - protected function downloadVersionData(&$data, &$error) - { - $url = $this->baseUrl.'/versions'; - $errFmt = 'The "%s" file could not be %s: %s'; - - if (!$json = $this->httpClient->get($url)) { - $error = sprintf($errFmt, $url, 'downloaded', $this->errHandler->message); - return false; - } - - if (!$data = json_decode($json, true)) { - $error = sprintf($errFmt, $url, 'json-decoded', $this->getJsonError()); - return false; - } - return true; - } - - /** - * A wrapper around the methods needed to download and save the phar - * - * @param string $url The versioned download url - * @param null|string $signature Set by method on successful download - * @param null|string $error Set by method on failure - * - * @return bool If the operation succeeded - */ - protected function downloadToTmp($url, &$signature, &$error) - { - $error = ''; - $errFmt = 'The "%s" file could not be downloaded: %s'; - $sigUrl = $url.'.sig'; - $this->errHandler->start(); - - if (!$fh = fopen($this->tmpFile, 'w')) { - $error = sprintf('Could not create file "%s": %s', $this->tmpFile, $this->errHandler->message); - - } elseif (!$this->getSignature($sigUrl, $signature)) { - $error = sprintf($errFmt, $sigUrl, $this->errHandler->message); - - } elseif (!fwrite($fh, $this->httpClient->get($url))) { - $error = sprintf($errFmt, $url, $this->errHandler->message); - } - - if (is_resource($fh)) { - fclose($fh); - } - $this->errHandler->stop(); - return empty($error); - } - - /** - * Verifies the downloaded file and saves it to the target location - * - * @param string $version The composer version downloaded - * @param string $signature The digital signature to check - * @param null|string $error Set by method on failure - * - * @return bool If the operation succeeded - */ - protected function verifyAndSave($version, $signature, &$error) - { - $error = ''; - - if (!$this->validatePhar($this->tmpFile, $pharError)) { - $error = 'The download is corrupt: '.$pharError; - - } elseif (!$this->verifySignature($version, $signature, $this->tmpFile)) { - $error = 'Signature mismatch, could not verify the phar file integrity'; - - } else { - $this->errHandler->start(); - - if (!rename($this->tmpFile, $this->target)) { - $error = sprintf('Could not write to file "%s": %s', $this->target, $this->errHandler->message); - } - chmod($this->target, 0755); - $this->errHandler->stop(); - } - - return empty($error); - } - - /** - * Parses an array of version data to match the required channel - * - * @param array $data Downloaded version data - * @param mixed $channel Version channel to use - * @param false|string $version Set by method - * @param mixed $url The versioned url, set by method - */ - protected function parseVersionData(array $data, $channel, &$version, &$url) - { - foreach ($data[$channel] as $candidate) { - if ($candidate['min-php'] <= PHP_VERSION_ID) { - $version = $candidate['version']; - $url = $this->baseUrl.$candidate['path']; - break; - } - } - - if (!$version) { - $error = sprintf( - 'None of the %d %s version(s) of Composer matches your PHP version (%s / ID: %d)', - count($data[$channel]), - $channel, - PHP_VERSION, - PHP_VERSION_ID - ); - throw new RuntimeException($error); - } - } - - /** - * Downloads the digital signature of required phar file - * - * @param string $url The signature url - * @param null|string $signature Set by method on success - * - * @return bool If the download succeeded - */ - protected function getSignature($url, &$signature) - { - if (!$result = $this->disableTls) { - $signature = $this->httpClient->get($url); - - if ($signature) { - $signature = json_decode($signature, true); - $signature = base64_decode($signature['sha384']); - $result = true; - } - } - - return $result; - } - - /** - * Verifies the signature of the downloaded phar - * - * @param string $version The composer versione - * @param string $signature The downloaded digital signature - * @param string $file The temp phar file - * - * @return bool If the operation succeeded - */ - protected function verifySignature($version, $signature, $file) - { - if (!$result = $this->disableTls) { - $path = preg_match('{^[0-9a-f]{40}$}', $version) ? $this->pubKeys['dev'] : $this->pubKeys['tags']; - $pubkeyid = openssl_pkey_get_public('file://'.$path); - - $result = 1 === openssl_verify( - file_get_contents($file), - $signature, - $pubkeyid, - $this->algo - ); - - openssl_free_key($pubkeyid); - } - - return $result; - } - - /** - * Validates the downloaded phar file - * - * @param string $pharFile The temp phar file - * @param null|string $error Set by method on failure - * - * @return bool If the operation succeeded - */ - protected function validatePhar($pharFile, &$error) - { - if (ini_get('phar.readonly')) { - return true; - } - - try { - // Test the phar validity - $phar = new Phar($pharFile); - // Free the variable to unlock the file - unset($phar); - $result = true; - - } catch (Exception $e) { - if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) { - throw $e; - } - $error = $e->getMessage(); - $result = false; - } - return $result; - } - - /** - * Returns a string representation of the last json error - * - * @return string The error string or code - */ - protected function getJsonError() - { - if (function_exists('json_last_error_msg')) { - return json_last_error_msg(); - } else { - return 'json_last_error = '.json_last_error(); - } - } - - /** - * Cleans up resources at the end of the installation - * - * @param bool $result If the installation succeeded - */ - protected function cleanUp($result) - { - if (!$result) { - // Output buffered errors - if ($this->quiet) { - $this->outputErrors(); - } - // Clean up stuff we created - $this->uninstall(); - } - } - - /** - * Outputs unique errors when in quiet mode - * - */ - protected function outputErrors() - { - $errors = explode(PHP_EOL, ob_get_clean()); - $shown = array(); - - foreach ($errors as $error) { - if ($error && !in_array($error, $shown)) { - out($error, 'error'); - $shown[] = $error; - } - } - } - - /** - * Uninstalls newly-created files and directories on failure - * - */ - protected function uninstall() - { - foreach (array_reverse($this->installs) as $target) { - if (is_file($target)) { - @unlink($target); - } elseif (is_dir($target)) { - @rmdir($target); - } - } - - if (file_exists($this->tmpFile)) { - @unlink($this->tmpFile); - } - } - - public static function getPKDev() - { - return <<message) { - $this->message .= PHP_EOL; - } - $this->message .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); - } - - /** - * Starts error-handling if not already active - * - * Any message is cleared - */ - public function start() - { - if (!$this->active) { - set_error_handler(array($this, 'handleError')); - $this->active = true; - } - $this->message = ''; - } - - /** - * Stops error-handling if active - * - * Any message is preserved until the next call to start() - */ - public function stop() - { - if ($this->active) { - restore_error_handler(); - $this->active = false; - } - } -} - -class NoProxyPattern -{ - private $composerInNoProxy = false; - private $rulePorts = array(); - - public function __construct($pattern) - { - $rules = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY); - - if ($matches = preg_grep('{getcomposer\.org(?::\d+)?}i', $rules)) { - $this->composerInNoProxy = true; - - foreach ($matches as $match) { - if (strpos($match, ':') !== false) { - list(, $port) = explode(':', $match); - $this->rulePorts[] = (int) $port; - } - } - } - } - - /** - * Returns true if NO_PROXY contains getcomposer.org - * - * @param string $url http(s)://getcomposer.org - * - * @return bool - */ - public function test($url) - { - if (!$this->composerInNoProxy) { - return false; - } - - if (empty($this->rulePorts)) { - return true; - } - - if (strpos($url, 'http://') === 0) { - $port = 80; - } else { - $port = 443; - } - - return in_array($port, $this->rulePorts); - } -} - -class HttpClient { - - private $options = array('http' => array()); - private $disableTls = false; - - public function __construct($disableTls = false, $cafile = false) - { - $this->disableTls = $disableTls; - if ($this->disableTls === false) { - if (!empty($cafile) && !is_dir($cafile)) { - if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) { - throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.'); - } - } - $options = $this->getTlsStreamContextDefaults($cafile); - $this->options = array_replace_recursive($this->options, $options); - } - } - - public function get($url) - { - $context = $this->getStreamContext($url); - $result = file_get_contents($url, false, $context); - - if ($result && extension_loaded('zlib')) { - $decode = false; - foreach ($http_response_header as $header) { - if (preg_match('{^content-encoding: *gzip *$}i', $header)) { - $decode = true; - continue; - } elseif (preg_match('{^HTTP/}i', $header)) { - $decode = false; - } - } - - if ($decode) { - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - $result = zlib_decode($result); - } else { - // work around issue with gzuncompress & co that do not work with all gzip checksums - $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); - } - - if (!$result) { - throw new RuntimeException('Failed to decode zlib stream'); - } - } - } - - return $result; - } - - protected function getStreamContext($url) - { - if ($this->disableTls === false) { - if (PHP_VERSION_ID < 50600) { - $this->options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST); - } - } - // Keeping the above mostly isolated from the code copied from Composer. - return $this->getMergedStreamContext($url); - } - - protected function getTlsStreamContextDefaults($cafile) - { - $ciphers = implode(':', array( - 'ECDHE-RSA-AES128-GCM-SHA256', - 'ECDHE-ECDSA-AES128-GCM-SHA256', - 'ECDHE-RSA-AES256-GCM-SHA384', - 'ECDHE-ECDSA-AES256-GCM-SHA384', - 'DHE-RSA-AES128-GCM-SHA256', - 'DHE-DSS-AES128-GCM-SHA256', - 'kEDH+AESGCM', - 'ECDHE-RSA-AES128-SHA256', - 'ECDHE-ECDSA-AES128-SHA256', - 'ECDHE-RSA-AES128-SHA', - 'ECDHE-ECDSA-AES128-SHA', - 'ECDHE-RSA-AES256-SHA384', - 'ECDHE-ECDSA-AES256-SHA384', - 'ECDHE-RSA-AES256-SHA', - 'ECDHE-ECDSA-AES256-SHA', - 'DHE-RSA-AES128-SHA256', - 'DHE-RSA-AES128-SHA', - 'DHE-DSS-AES128-SHA256', - 'DHE-RSA-AES256-SHA256', - 'DHE-DSS-AES256-SHA', - 'DHE-RSA-AES256-SHA', - 'AES128-GCM-SHA256', - 'AES256-GCM-SHA384', - 'AES128-SHA256', - 'AES256-SHA256', - 'AES128-SHA', - 'AES256-SHA', - 'AES', - 'CAMELLIA', - 'DES-CBC3-SHA', - '!aNULL', - '!eNULL', - '!EXPORT', - '!DES', - '!RC4', - '!MD5', - '!PSK', - '!aECDH', - '!EDH-DSS-DES-CBC3-SHA', - '!EDH-RSA-DES-CBC3-SHA', - '!KRB5-DES-CBC3-SHA', - )); - - /** - * CN_match and SNI_server_name are only known once a URL is passed. - * They will be set in the getOptionsForUrl() method which receives a URL. - * - * cafile or capath can be overridden by passing in those options to constructor. - */ - $options = array( - 'ssl' => array( - 'ciphers' => $ciphers, - 'verify_peer' => true, - 'verify_depth' => 7, - 'SNI_enabled' => true, - ) - ); - - /** - * Attempt to find a local cafile or throw an exception. - * The user may go download one if this occurs. - */ - if (!$cafile) { - $cafile = self::getSystemCaRootBundlePath(); - } - if (is_dir($cafile)) { - $options['ssl']['capath'] = $cafile; - } elseif ($cafile) { - $options['ssl']['cafile'] = $cafile; - } else { - throw new RuntimeException('A valid cafile could not be located automatically.'); - } - - /** - * Disable TLS compression to prevent CRIME attacks where supported. - */ - if (version_compare(PHP_VERSION, '5.4.13') >= 0) { - $options['ssl']['disable_compression'] = true; - } - - return $options; - } - - /** - * function copied from Composer\Util\StreamContextFactory::getContext - * - * Any changes should be applied there as well, or backported here. - * - * @param string $url URL the context is to be used for - * @return resource Default context - * @throws \RuntimeException if https proxy required and OpenSSL uninstalled - */ - protected function getMergedStreamContext($url) - { - $options = $this->options; - - // Handle HTTP_PROXY/http_proxy on CLI only for security reasons - if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { - $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); - } - - // Prefer CGI_HTTP_PROXY if available - if (!empty($_SERVER['CGI_HTTP_PROXY'])) { - $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']); - } - - // Override with HTTPS proxy if present and URL is https - if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) { - $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']); - } - - // Remove proxy if URL matches no_proxy directive - if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) { - $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']); - if ($pattern->test($url)) { - unset($proxy); - } - } - - if (!empty($proxy)) { - $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : ''; - $proxyURL .= isset($proxy['host']) ? $proxy['host'] : ''; - - if (isset($proxy['port'])) { - $proxyURL .= ":" . $proxy['port']; - } elseif ('http://' == substr($proxyURL, 0, 7)) { - $proxyURL .= ":80"; - } elseif ('https://' == substr($proxyURL, 0, 8)) { - $proxyURL .= ":443"; - } - - // http(s):// is not supported in proxy - $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); - - if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) { - throw new RuntimeException('You must enable the openssl extension to use a proxy over https'); - } - - $options['http'] = array( - 'proxy' => $proxyURL, - ); - - // set request_fulluri, depending on default requirements and the environment - switch (parse_url($url, PHP_URL_SCHEME)) { - case 'http': // default is true for HTTP, check if explicitly disabled - $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI'); - if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) { - $options['http']['request_fulluri'] = true; - } - break; - case 'https': // default is false for HTTPS, check if explicitly enabled - $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI'); - if (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv) { - $options['http']['request_fulluri'] = true; - } - break; - } - - // handle proxy auth if present - if (isset($proxy['user'])) { - $auth = rawurldecode($proxy['user']); - if (isset($proxy['pass'])) { - $auth .= ':' . rawurldecode($proxy['pass']); - } - $auth = base64_encode($auth); - - $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n"; - } - } - - if (isset($options['http']['header'])) { - $options['http']['header'] .= "Connection: close\r\n"; - } else { - $options['http']['header'] = "Connection: close\r\n"; - } - if (extension_loaded('zlib')) { - $options['http']['header'] .= "Accept-Encoding: gzip\r\n"; - } - $options['http']['header'] .= "User-Agent: ".COMPOSER_INSTALLER."\r\n"; - $options['http']['protocol_version'] = 1.1; - $options['http']['timeout'] = 600; - - return stream_context_create($options); - } - - /** - * This method was adapted from Sslurp. - * https://github.com/EvanDotPro/Sslurp - * - * (c) Evan Coury - * - * For the full copyright and license information, please see below: - * - * Copyright (c) 2013, Evan Coury - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - public static function getSystemCaRootBundlePath() - { - static $caPath = null; - - if ($caPath !== null) { - return $caPath; - } - - // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. - // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. - $envCertFile = getenv('SSL_CERT_FILE'); - if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) { - return $caPath = $envCertFile; - } - - // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. - // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. - $envCertDir = getenv('SSL_CERT_DIR'); - if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) { - return $caPath = $envCertDir; - } - - $configured = ini_get('openssl.cafile'); - if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) { - return $caPath = $configured; - } - - $configured = ini_get('openssl.capath'); - if ($configured && is_dir($configured) && is_readable($configured)) { - return $caPath = $configured; - } - - $caBundlePaths = array( - '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) - '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) - '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) - '/usr/ssl/certs/ca-bundle.crt', // Cygwin - '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package - '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) - '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? - '/etc/ssl/cert.pem', // OpenBSD - '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x - '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package - '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package - ); - - foreach ($caBundlePaths as $caBundle) { - if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) { - return $caPath = $caBundle; - } - } - - foreach ($caBundlePaths as $caBundle) { - $caBundle = dirname($caBundle); - if (is_dir($caBundle) && glob($caBundle.'/*')) { - return $caPath = $caBundle; - } - } - - return $caPath = false; - } - - public static function getPackagedCaFile() - { - return <<addNamedFilter(NamedFilter::fromString('symfony/framework-bundle')) + ->addNamedFilter(NamedFilter::fromString('symfony/yaml')); +}; diff --git a/composer.json b/composer.json index 02a4c93..feffc61 100644 --- a/composer.json +++ b/composer.json @@ -28,28 +28,29 @@ } ], "require": { - "php": "~8.2", - "symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0", - "symfony/config": "^5.4 || ^6.4 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.4 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", + "php": "^8.2", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", "symfony/event-dispatcher-contracts": "^2.0 || ^3.0", - "symfony/http-foundation": "^5.4 || ^6.4 || ^7.0", + "symfony/http-foundation": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", "twig/twig": "^3.8" }, "require-dev": { - "doctrine/coding-standard": "~12.0", + "doctrine/coding-standard": "^12.0", "roave/security-advisories": "dev-master", - "squizlabs/php_codesniffer": "~3.7", - "phpstan/phpstan": "~1.10", - "phpstan/phpstan-deprecation-rules": "~1.1", - "phpstan/phpstan-phpunit": "~1.3", - "phpstan/phpstan-strict-rules": "~1.5", - "maglnet/composer-require-checker": "~4.7", - "phpunit/phpunit": "~10.5", - "symfony/var-dumper": "~7.0", - "dg/bypass-finals": "~1.5", + "squizlabs/php_codesniffer": "^3.7", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "maglnet/composer-require-checker": "^4.7", + "phpunit/phpunit": "^10.5", + "symfony/var-dumper": "^7.0", + "dg/bypass-finals": "^1.5", "icanhazstring/composer-unused": "^0.8" }, "autoload": { @@ -70,7 +71,7 @@ "@phpstan", "@phpunit" ], - "unused": "composer unused --excludeDir=vendor", + "unused": "vendor/bin/composer-unused", "phpstan": "./vendor/bin/phpstan analyse ./src", "phpstan-update-baseline": "phpstan analyse --ansi --generate-baseline phpstan-baseline.neon", "crc": "./vendor/bin/composer-require-checker --config-file=./composer-require-checker.json", diff --git a/phpcs.xml b/phpcs.xml index e028e59..46766bc 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -14,7 +14,6 @@ - diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2455250..f8b0591 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,9 +10,3 @@ parameters: paths: - src - tests - - excludes_analyse: - - src/DependencyInjection/Configuration - - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4214a7d..9a3d3c2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,21 +1,15 @@ - - - - - - tests - - - - - - src - - + + + + + tests + + + + + src + + diff --git a/src/DependencyInjection/Compiler/EventHandlerCompiler.php b/src/DependencyInjection/Compiler/EventHandlerCompiler.php index 9baeef2..c344047 100644 --- a/src/DependencyInjection/Compiler/EventHandlerCompiler.php +++ b/src/DependencyInjection/Compiler/EventHandlerCompiler.php @@ -4,19 +4,17 @@ namespace Shapecode\Bundle\TwigTemplateEventBundle\DependencyInjection\Compiler; -use Shapecode\Bundle\TwigTemplateEventBundle\Manager\HandlerManagerInterface; +use Shapecode\Bundle\TwigTemplateEventBundle\Manager\HandlerManager; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; final class EventHandlerCompiler implements CompilerPassInterface { - /** - * @inheritDoc - */ + /** @inheritDoc */ public function process(ContainerBuilder $container): void { - $manager = $container->findDefinition(HandlerManagerInterface::class); + $manager = $container->findDefinition(HandlerManager::class); $tags = $container->findTaggedServiceIds('shapecode_twig_template_event.handler'); foreach ($tags as $id => $config) { diff --git a/src/DependencyInjection/ShapecodeTwigTemplateEventExtension.php b/src/DependencyInjection/ShapecodeTwigTemplateEventExtension.php index 4037bc1..73bc26b 100644 --- a/src/DependencyInjection/ShapecodeTwigTemplateEventExtension.php +++ b/src/DependencyInjection/ShapecodeTwigTemplateEventExtension.php @@ -4,6 +4,7 @@ namespace Shapecode\Bundle\TwigTemplateEventBundle\DependencyInjection; +use Shapecode\Bundle\TwigTemplateEventBundle\Event\Handler\HandlerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; @@ -11,12 +12,14 @@ final class ShapecodeTwigTemplateEventExtension extends Extension { - /** - * @inheritdoc - */ + /** @inheritdoc */ public function load(array $configs, ContainerBuilder $container): void { $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.yml'); + + $container + ->registerForAutoconfiguration(HandlerInterface::class) + ->addTag('shapecode_twig_template_event.handler'); } } diff --git a/src/Event/Code/TwigEventCode.php b/src/Event/Code/TwigEventCode.php index c9636bd..0bcc967 100644 --- a/src/Event/Code/TwigEventCode.php +++ b/src/Event/Code/TwigEventCode.php @@ -6,12 +6,9 @@ abstract class TwigEventCode implements TwigEventCodeInterface { - /** @var int */ - private $priority; - - public function __construct(int $priority = 0) - { - $this->priority = $priority; + public function __construct( + private int $priority = 0, + ) { } public function getPriority(): int diff --git a/src/Event/Code/TwigEventInclude.php b/src/Event/Code/TwigEventInclude.php index 7a5ad9c..d411400 100644 --- a/src/Event/Code/TwigEventInclude.php +++ b/src/Event/Code/TwigEventInclude.php @@ -8,24 +8,17 @@ final class TwigEventInclude extends TwigEventCode { - /** @var string */ - private $template; + private string $template; - /** @var array */ - private $parameters; - - /** - * @param array $parameters - */ + /** @param array $parameters */ public function __construct( string $templateString, - array $parameters = [], - int $priority = 0 + private array $parameters = [], + int $priority = 0, ) { parent::__construct($priority); - $this->template = $templateString; - $this->parameters = $parameters; + $this->template = $templateString; } public function getTemplate(): string @@ -38,17 +31,13 @@ public function setTemplate(string $template): void $this->template = $template; } - /** - * @return array - */ + /** @return array */ public function getParameters(): array { return $this->parameters; } - /** - * @param array $parameters - */ + /** @param array $parameters */ public function setParameters(array $parameters = []): void { $this->parameters = $parameters; diff --git a/src/Event/Code/TwigEventRender.php b/src/Event/Code/TwigEventRender.php index af3809a..0ab023b 100644 --- a/src/Event/Code/TwigEventRender.php +++ b/src/Event/Code/TwigEventRender.php @@ -8,35 +8,18 @@ final class TwigEventRender extends TwigEventCode { - /** @var string */ - private $controller; - - /** @var array */ - private $attributes; - - /** @var array */ - private $query; - - /** @var string */ - private $strategy; - /** * @param array $attributes * @param array $query */ public function __construct( - string $controller, - array $attributes = [], + private string $controller, + private array $attributes = [], int $priority = 0, - array $query = [], - string $strategy = 'inline' + private array $query = [], + private string $strategy = 'inline', ) { parent::__construct($priority); - - $this->controller = $controller; - $this->attributes = $attributes; - $this->query = $query; - $this->strategy = $strategy; } public function getController(): string @@ -49,33 +32,25 @@ public function setController(string $controller): void $this->controller = $controller; } - /** - * @return array - */ + /** @return array */ public function getAttributes(): array { return $this->attributes; } - /** - * @param array $attributes - */ + /** @param array $attributes */ public function setAttributes(array $attributes = []): void { $this->attributes = $attributes; } - /** - * @return array - */ + /** @return array */ public function getQuery(): array { return $this->query; } - /** - * @param array $query - */ + /** @param array $query */ public function setQuery(array $query = []): void { $this->query = $query; diff --git a/src/Event/Code/TwigEventString.php b/src/Event/Code/TwigEventString.php index dae9657..7b6f633 100644 --- a/src/Event/Code/TwigEventString.php +++ b/src/Event/Code/TwigEventString.php @@ -13,30 +13,19 @@ final class TwigEventString extends TwigEventCode { - /** @var string */ - private $templateString; - - /** @var array */ - private $parameters; - - /** - * @param array $parameters - */ - public function __construct(string $templateString, array $parameters = [], int $priority = 0) + /** @param array $parameters */ + public function __construct(private string $templateString, private array $parameters = [], int $priority = 0) { if (! class_exists(StringLoader::class)) { throw new LogicException(sprintf( 'You have to install %s or %s package to use %s', 'shapecode/twig-string-loader-bundle', 'shapecode/twig-string-loader', - self::class + self::class, )); } parent::__construct($priority); - - $this->templateString = $templateString; - $this->parameters = $parameters; } public function getTemplateString(): string @@ -49,17 +38,13 @@ public function setTemplateString(string $templateString): void $this->templateString = $templateString; } - /** - * @return array - */ + /** @return array */ public function getParameters(): array { return $this->parameters; } - /** - * @param array $parameters - */ + /** @param array $parameters */ public function setParameters(array $parameters = []): void { $this->parameters = $parameters; diff --git a/src/Event/Handler/HandlerInterface.php b/src/Event/Handler/HandlerInterface.php index 0f25b03..cae4690 100644 --- a/src/Event/Handler/HandlerInterface.php +++ b/src/Event/Handler/HandlerInterface.php @@ -7,14 +7,16 @@ use Shapecode\Bundle\TwigTemplateEventBundle\Event\Code\TwigEventCodeInterface; use Twig\Environment; +/** @template T of TwigEventCodeInterface */ interface HandlerInterface { /** + * @param T $code * @param array $context */ public function handle( TwigEventCodeInterface $code, Environment $env, - array $context = [] + array $context = [], ): string; } diff --git a/src/Event/Handler/IncludeHandler.php b/src/Event/Handler/IncludeHandler.php index d37316a..e87929d 100644 --- a/src/Event/Handler/IncludeHandler.php +++ b/src/Event/Handler/IncludeHandler.php @@ -9,23 +9,18 @@ use Twig\Environment; use function array_replace_recursive; -use function assert; +/** @template-implements HandlerInterface */ final class IncludeHandler implements HandlerInterface { - /** - * @inheritDoc - */ + /** @inheritDoc */ public function handle( TwigEventCodeInterface $code, Environment $env, - array $context = [] + array $context = [], ): string { - assert($code instanceof TwigEventInclude); - $parameters = array_replace_recursive($context, $code->getParameters()); - $template = $env->resolveTemplate($code->getTemplate()); - return $template->render($parameters); + return $env->resolveTemplate($code->getTemplate())->render($parameters); } } diff --git a/src/Event/Handler/RenderHandler.php b/src/Event/Handler/RenderHandler.php index 85a0de8..c53e7cd 100644 --- a/src/Event/Handler/RenderHandler.php +++ b/src/Event/Handler/RenderHandler.php @@ -10,25 +10,17 @@ use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Twig\Environment; -use function assert; - +/** @template-implements HandlerInterface */ final class RenderHandler implements HandlerInterface { - /** @var FragmentHandler */ - private $fragment; - - public function __construct(FragmentHandler $fragment) - { - $this->fragment = $fragment; + public function __construct( + private readonly FragmentHandler $fragment, + ) { } - /** - * @inheritDoc - */ + /** @inheritDoc */ public function handle(TwigEventCodeInterface $code, Environment $env, array $context = []): string { - assert($code instanceof TwigEventRender); - $reference = new ControllerReference($code->getController(), $code->getAttributes(), $code->getQuery()); return (string) $this->fragment->render($reference, $code->getStrategy()); diff --git a/src/Event/Handler/StringHandler.php b/src/Event/Handler/StringHandler.php index 0d54baa..ea5c50e 100644 --- a/src/Event/Handler/StringHandler.php +++ b/src/Event/Handler/StringHandler.php @@ -9,20 +9,16 @@ use Twig\Environment; use function array_replace_recursive; -use function assert; +/** @template-implements HandlerInterface */ final class StringHandler implements HandlerInterface { - /** - * @inheritdoc - */ + /** @inheritdoc */ public function handle( TwigEventCodeInterface $code, Environment $env, - array $context = [] + array $context = [], ): string { - assert($code instanceof TwigEventString); - $parameters = array_replace_recursive($context, $code->getParameters()); return $env->render($code->getTemplateString(), $parameters); diff --git a/src/Event/TwigTemplateEvent.php b/src/Event/TwigTemplateEvent.php index 07a3fed..084a5bf 100644 --- a/src/Event/TwigTemplateEvent.php +++ b/src/Event/TwigTemplateEvent.php @@ -13,41 +13,21 @@ class TwigTemplateEvent extends Event { - /** @var Environment */ - private $environment; - - /** @var array */ - private $parameters; - - /** @var array */ - private $context; - - /** @var string */ - private $eventName; - - /** @var RequestStack */ - private $request; - - /** @var TwigEventCodeInterface[] */ - private $codes; + /** @var array */ + private array $codes; /** * @param array $context * @param array $parameters */ public function __construct( - string $eventName, - Environment $environment, - array $context, - array $parameters, - RequestStack $request + private string $eventName, + private Environment $environment, + private array $context, + private array $parameters, + private RequestStack $request, ) { - $this->eventName = $eventName; - $this->environment = $environment; - $this->context = $context; - $this->parameters = $parameters; - $this->request = $request; - $this->codes = []; + $this->codes = []; } public function getEnvironment(): Environment @@ -55,17 +35,13 @@ public function getEnvironment(): Environment return $this->environment; } - /** - * @return array - */ + /** @return array */ public function getParameters(): array { return $this->parameters; } - /** - * @return array - */ + /** @return array */ public function getContext(): array { return $this->context; @@ -92,9 +68,7 @@ public function addCode(TwigEventCodeInterface $code): void $this->codes[] = $code; } - /** - * @return TwigEventCodeInterface[] - */ + /** @return TwigEventCodeInterface[] */ public function getCodes(): array { return $this->codes; diff --git a/src/Manager/HandlerManager.php b/src/Manager/HandlerManager.php index 4ead1a2..40d48f4 100644 --- a/src/Manager/HandlerManager.php +++ b/src/Manager/HandlerManager.php @@ -5,20 +5,21 @@ namespace Shapecode\Bundle\TwigTemplateEventBundle\Manager; use RuntimeException; +use Shapecode\Bundle\TwigTemplateEventBundle\Event\Code\TwigEventCodeInterface; use Shapecode\Bundle\TwigTemplateEventBundle\Event\Handler\HandlerInterface; use function array_key_exists; -use function get_class; use function sprintf; -class HandlerManager implements HandlerManagerInterface +class HandlerManager { - /** @var HandlerInterface[] */ - protected $handlers = []; + /** @var array> */ + protected array $handlers = []; + /** @param HandlerInterface $handler */ public function addHandler(HandlerInterface $handler): void { - $name = get_class($handler); + $name = $handler::class; if (array_key_exists($name, $this->handlers)) { throw new RuntimeException(sprintf('handler %s already exists', $name), 1596984981559); @@ -27,6 +28,7 @@ public function addHandler(HandlerInterface $handler): void $this->handlers[$name] = $handler; } + /** @return HandlerInterface */ public function getHandler(string $name): HandlerInterface { if (! array_key_exists($name, $this->handlers)) { diff --git a/src/Manager/HandlerManagerInterface.php b/src/Manager/HandlerManagerInterface.php deleted file mode 100644 index abb95c4..0000000 --- a/src/Manager/HandlerManagerInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -dispatcher = $dispatcher; - $this->requestStack = $requestStack; - $this->manager = $manager; } /** - * @inheritdoc + * @param array $parameters + * @param array $context */ public function handleEvent( string $name, Environment $environment, array $parameters = [], - array $context = [] + array $context = [], ): string { $event = new TwigTemplateEvent($name, $environment, $context, $parameters, $this->requestStack); @@ -52,18 +41,16 @@ public function handleEvent( return $this->render($event); } - protected function render(TwigTemplateEvent $event): string + private function render(TwigTemplateEvent $event): string { return implode('', $this->compile( $event, - ...$this->sort(...$event->getCodes()) + ...$this->sort(...$event->getCodes()), )); } - /** - * @return TwigEventCodeInterface[] - */ - protected function sort(TwigEventCodeInterface ...$codes): array + /** @return array */ + private function sort(TwigEventCodeInterface ...$codes): array { usort($codes, static function (TwigEventCodeInterface $a, TwigEventCodeInterface $b): int { return $a->getPriority() <=> $b->getPriority(); @@ -72,10 +59,8 @@ protected function sort(TwigEventCodeInterface ...$codes): array return $codes; } - /** - * @return string[] - */ - protected function compile(TwigTemplateEvent $event, TwigEventCodeInterface ...$codes): array + /** @return array */ + private function compile(TwigTemplateEvent $event, TwigEventCodeInterface ...$codes): array { return array_map(function (TwigEventCodeInterface $code) use ($event): string { return $this->manager->getHandler($code->getHandlerName())->handle($code, $event->getEnvironment(), $event->getContext()); diff --git a/src/Services/EventServiceInterface.php b/src/Services/EventServiceInterface.php deleted file mode 100644 index 6f420b7..0000000 --- a/src/Services/EventServiceInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - $parameters - * @param array $context - */ - public function handleEvent(string $name, Environment $environment, array $parameters = [], array $context = []): string; -} diff --git a/src/ShapecodeTwigTemplateEventBundle.php b/src/ShapecodeTwigTemplateEventBundle.php index 757da1e..8416c96 100644 --- a/src/ShapecodeTwigTemplateEventBundle.php +++ b/src/ShapecodeTwigTemplateEventBundle.php @@ -10,9 +10,7 @@ final class ShapecodeTwigTemplateEventBundle extends Bundle { - /** - * @inheritDoc - */ + /** @inheritDoc */ public function build(ContainerBuilder $container): void { parent::build($container); diff --git a/src/Twig/EventExtension.php b/src/Twig/EventExtension.php index 94a2d59..7de9ed3 100644 --- a/src/Twig/EventExtension.php +++ b/src/Twig/EventExtension.php @@ -4,24 +4,19 @@ namespace Shapecode\Bundle\TwigTemplateEventBundle\Twig; -use Shapecode\Bundle\TwigTemplateEventBundle\Services\EventServiceInterface; +use Shapecode\Bundle\TwigTemplateEventBundle\Services\EventService; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; final class EventExtension extends AbstractExtension { - /** @var EventServiceInterface */ - private $eventService; - - public function __construct(EventServiceInterface $eventService) - { - $this->eventService = $eventService; + public function __construct( + private readonly EventService $eventService, + ) { } - /** - * @inheritDoc - */ + /** @inheritDoc */ public function getFunctions(): array { return [ diff --git a/tests/Services/EventServiceTest.php b/tests/Services/EventServiceTest.php index 2c37dd1..3e125e3 100644 --- a/tests/Services/EventServiceTest.php +++ b/tests/Services/EventServiceTest.php @@ -14,8 +14,6 @@ use Symfony\Component\HttpFoundation\RequestStack; use Twig\Environment; -use function get_class; - final class EventServiceTest extends TestCase { public function testHandleEvent(): void @@ -26,7 +24,7 @@ public function testHandleEvent(): void $handler = $this->createMock(HandlerInterface::class); $handler->method('handle')->willReturnOnConsecutiveCalls('foo', 'bar'); - $className = get_class($handler); + $className = $handler::class; $handlerManager = new HandlerManager(); $handlerManager->addHandler($handler); @@ -34,7 +32,7 @@ public function testHandleEvent(): void $sut = new EventService( $eventDispatcher, $requestStack, - $handlerManager + $handlerManager, ); $code1 = $this->createMock(TwigEventCodeInterface::class); @@ -72,7 +70,7 @@ public function testHandleEvent(): void ], [ 'context' => 'bar', - ] + ], ); self::assertSame('foobar', $compiled);