coenjacobs / mozart

Developers tool for WordPress plugins: Wraps all your projects dependencies in your own namespace, in order to prevent conflicts with other plugins loading the same dependencies in different versions.
https://coenjacobs.me/projects/mozart/
MIT License
434 stars 55 forks source link

classmap_prefix duplication and other issues with classmaps #106

Open olegabr opened 3 years ago

olegabr commented 3 years ago
{
    "name": "ethereumico/ether-and-erc20-tokens-woocommerce-payment-gateway",
    "description": "Ether and ERC20 tokens WooCommerce Payment Gateway enables customers to pay with Ether or any ERC20 or ERC223 token on your WooCommerce store.",
    "type": "project",
    "repositories": [
       {
          "type": "vcs",
          "url": "https://github.com/olegabr/web3.php"
       },
       {
          "type": "vcs",
          "url": "https://github.com/olegabr/php-keccak"
       }
    ],
    "require": {
        "olegabr/web3.php": "~0.1",
        "freemius/wordpress-sdk": "^2.4",
        "woocommerce/action-scheduler": "dev-master"
    },
    "license": "GPLv3",
    "authors": [
        {
            "name": "EthereumICO",
            "email": "ethereumicoio@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require-dev": {
        "coenjacobs/mozart": "dev-master"
    },
    "extra": {
        "mozart": {
            "dep_namespace": "\\Ethereumico\\Epg\\Dependencies\\",
            "dep_directory": "/vendor-epg/",
            "classmap_directory": "/vendor-epg/classes/",
            "classmap_prefix": "ether_and_erc20_tokens_woocommerce_payment_gateway_",
            "packages": [
                "freemius/wordpress-sdk",
                "guzzlehttp/guzzle",
                "guzzlehttp/promises",
                "guzzlehttp/psr7",
                "olegabr/keccak",
                "olegabr/web3.php",
                "paragonie/constant_time_encoding",
                "paragonie/random_compat",
                "phpseclib/phpseclib",
                "psr/http-message",
                "ralouphie/getallheaders",
                "symfony/polyfill-intl-idn",
                "symfony/polyfill-intl-normalizer",
                "symfony/polyfill-mbstring",
                "symfony/polyfill-php72",
                "woocommerce/action-scheduler"
            ],
            "delete_vendor_directories": true
        }
    },
    "scripts": {
        "post-install-cmd": [
            "\"vendor/bin/mozart\" compose",
            "composer dump-autoload"
        ],
        "post-update-cmd": [
            "\"vendor/bin/mozart\" compose",
            "composer dump-autoload"
        ]
    }
}

It created file /home/USER/PROJECT/vendor-epg/classes/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php with contents:

<?php

class ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_Normalizer extends Symfony\Polyfill\Intl\ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_Normalizer\Normalizer
{

Three issues here:

  1. class prefix added 4 times
  2. Symfony\Polyfill\Intl\ namespace is not prefixed with Ethereumico\Epg\Dependencies\
  3. The last namespace before class name is prefixed with class name prefix (4 times of course): ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_ether_and_erc20_tokens_woocommerce_payment_gateway_Normalizer

The /home/USER/PROJECT/vendor-epg/Symfony/Polyfill/Intl/Normalizer/Normalizer.php file is defined correctly as:


<?php

namespace \Ethereumico\Epg\Dependencies\Symfony\Polyfill\Intl\Normalizer;

class Normalizer
BrianHenryIE commented 3 years ago

The following ClassmapReplacer.php will skip classnames that already begin with the prefix.

<?php
/**
 * The purpose of this file is to find and update classnames (and interfaces...) in their declarations.
 * Those replaced are recorded and their uses elsewhere are updated in a later step.
 */

namespace CoenJacobs\Mozart\Replace;

class ClassmapReplacer extends BaseReplacer
{

    /** @var string[] */
    public $replacedClasses = [];

    /** @var string */
    public $classmap_prefix;

    public function replace($contents)
    {

        return preg_replace_callback(
            "
    /                       # Start the pattern
        namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*?(?=namespace|$) 
                            # Look for a preceeding namespace declaration, up until 
                            # a potential second namespace declaration
        |                   # if found, match that much before repeating the search 
                            # on the remainder of the string
        [^a-zA-Z0-9_\x7f-\xff]+         # After a non-class character,
        {$this->classmap_prefix}        # match an already prefixed classname
        [a-zA-Z0-9_\x7f-\xff]+          # before continuing on the remainder of the string
        |
        (?:abstract\sclass|class|interface)\s+  # Look behind for class, abstract class, interface
        ([a-zA-Z0-9_\x7f-\xff]+)        # Match the word until the first 
                            # non-classname-valid character
        \s?                 # Allow a space after
        (?:{|extends|implements|\n)     # Class declaration can be followed by {, extends, 
                            # implements, or a new line
    /sx", //                    # dot matches newline, ignore whitespace in regex.
            function ($matches) use ($contents) {

                // If we're inside a namespace other than the global namesspace, just return.
                if (preg_match('/^namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*/', $matches[0])) {
                    return $matches[0];
                }

                if (preg_match("/[^a-zA-Z0-9_\x7f-\xff]+{$this->classmap_prefix}[a-zA-Z0-9_\x7f-\xff]+/", $matches[0])) {
                    return $matches[0];
                }

                // The prepended class name.
                $replace = $this->classmap_prefix . $matches[1];
                $this->saveReplacedClass($matches[1], $replace);
                return str_replace($matches[1], $replace, $matches[0]);
            },
            $contents
        );
    }

    public function saveReplacedClass($classname, $replacedName)
    {
        $this->replacedClasses[ $classname ] = $replacedName;
    }
}

It is PR #101 with this added:

[^a-zA-Z0-9_\x7f-\xff]+         # After a non-class character,
{$this->classmap_prefix}        # match an already prefixed classname
[a-zA-Z0-9_\x7f-\xff]+          # before continuing on the remainder of the string
|

Updating Replacer.php:125 to :

'/(.*)([^a-zA-Z0-9_\x7f-\xff\\\\])'. $original . '([^a-zA-Z0-9_\x7f-\xff])/U',

fixes the extends incorrect replacement but needs a bit more thought and testing before making a PR.

PhpStorm was highlighting the namespace in Symphony/Polyfill/Intl/Normalizer/Normalizer.php with "undefined namespace Intl" and "undefined constant Normalizer". I changed composer.json's "dep_namespace": "\\Ethereumico\\Epg\\Dependencies\\", to "dep_namespace": "Ethereumico\\Epg\\Dependencies\\", and the message went away.

coenjacobs commented 3 years ago

That PR that was referred above is now released as part of Mozart 0.6.0. @olegabr can you confirm this is now resolved?