php-scoper icon indicating copy to clipboard operation
php-scoper copied to clipboard

Exclude gettext function "__" seems to have side effects

Open hugofintecture opened this issue 4 years ago • 4 comments

Bug report

Question Answer
PHP-Scoper version 0.17.2
PHP version 7.4.28
Platform with version WSL 2

When adding the "__" function (gettext for translations) in exclude-functions array, it seems to have unwanted-side effects. This function is used for example in WordPress plugin in my case. This function should be outside the namespace to work.

In this case for example, during scoping, with the library php-http/guzzle6-adapter this lead to at least one error:

/vendor/php-http/guzzle6-adapter/src/Client.php

At line 65 there is this: $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler());

While it should be (and it is ok when removing "__" from the exclude array): $handlerStack = new HandlerStack(\FintectureWC\GuzzleHttp\choose_handler());

Maybe a side effect with the "_" character?

scoper.inc.php
<?php

declare(strict_types=1);

use Isolated\Symfony\Component\Finder\Finder;

// You can do your own things here, e.g. collecting symbols to expose dynamically
// or files to exclude.
// However beware that this file is executed by PHP-Scoper, hence if you are using
// the PHAR it will be loaded by the PHAR. So it is highly recommended to avoid
// to auto-load any code here: it can result in a conflict or even corrupt
// the PHP-Scoper analysis.

return [
   // The prefix configuration. If a non null value is be used, a random prefix
   // will be generated instead.
   //
   // For more see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#prefix
   'prefix' => 'FintectureWC',

   // By default when running php-scoper add-prefix, it will prefix all relevant code found in the current working
   // directory. You can however define which files should be scoped by defining a collection of Finders in the
   // following configuration key.
   //
   // This configuration entry is completely ignored when using Box.
   //
   // For more see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#finders-and-paths
   'finders' => [
       Finder::create()->files()->in('src'),
       Finder::create()
           ->files()
           ->ignoreVCS(true)
           ->notName('/LICENSE|.*\\.md|.*\\.dist|Makefile|composer\\.json|composer\\.lock/')
           ->exclude([
               'doc',
               'test',
               'test_old',
               'tests',
               'Tests',
               'vendor-bin',
               'examples'
           ])
           ->in('vendor'),
       Finder::create()->append([
           'composer.json',
           'composer.lock'
       ]),
   ],

   // List of excluded files, i.e. files for which the content will be left untouched.
   // Paths are relative to the configuration file unless if they are already absolute
   //
   // For more see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#patchers
   'exclude-files' => [
       'src/includes/custom_wc_inputs/button.php',
       'src/includes/custom_wc_inputs/file_input.php',
       'src/includes/stats.php',
       'src/includes/webhook.php',
       'src/includes/logger.php',
       'src/includes/post-payment-validator.php',
       'src/views/checkout-description.php',
   ],

   // When scoping PHP files, there will be scenarios where some of the code being scoped indirectly references the
   // original namespace. These will include, for example, strings or string manipulations. PHP-Scoper has limited
   // support for prefixing such strings. To circumvent that, you can define patchers to manipulate the file to your
   // heart contents.
   //
   // For more see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#patchers
   'patchers' => [
       static function (string $filePath, string $prefix, string $contents): string {
           // Change the contents here.

           return $contents;
       },
   ],

   // List of symbols to consider internal i.e. to leave untouched.
   //
   // For more information see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#excluded-symbols
   'exclude-namespaces' => [
       // 'Acme\Foo'                     // The Acme\Foo namespace (and sub-namespaces)
       // '~^PHPUnit\\\\Framework$~',    // The whole namespace PHPUnit\Framework (but not sub-namespaces)
       // '~^$~',                        // The root namespace only
       // '',                            // Any namespace
   ],
   'exclude-classes' => [
       'WC_Payment_Gateway',
       'WC_Order'
   ],
   'exclude-functions' => [
       '__',
       'add_action',
       'add_filter',
       'add_post_meta',
       'admin_url',
       'apply_filters',
       'current_user_can',
       'esc_attr',
       'get_bloginfo',
       'get_option',
       'is_admin',
       'is_checkout',
       'plugin_basename',
       'plugin_dir_path',
       'plugin_dir_url',
       'plugins_url',
       'register_activation_hook',
       'register_deactivation_hook',
       'register_uninstall_hook',
       'sanitize_email',
       'sanitize_text_field',
       'site_url',
       'wc_add_notice',
       'wc_get_checkout_url',
       'wc_get_order',
       'wc_get_price_decimals',
       'wc_get_template',
       'wc_print_notice',
       'wp_die',
       'wp_doing_ajax',
       'wp_enqueue_media',
       'wp_enqueue_script',
       'wp_enqueue_style',
       'wp_is_mobile',
       'wp_kses_post',
       'wp_localize_script',
       'wp_parse_args',
       'wp_redirect',
       'wp_register_script',
       'wp_register_style',
       'wp_safe_redirect'
   ],
   'exclude-constants' => [
       // 'STDIN',
   ],

   // List of symbols to expose.
   //
   // For more information see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#exposed-symbols
   'expose-global-constants' => true,
   'expose-global-classes' => true,
   'expose-global-functions' => true,
   'expose-namespaces' => [
       // 'Acme\Foo'                     // The Acme\Foo namespace (and sub-namespaces)
       // '~^PHPUnit\\\\Framework$~',    // The whole namespace PHPUnit\Framework (but not sub-namespaces)
       // '~^$~',                        // The root namespace only
       // '',                            // Any namespace
   ],
   'expose-classes' => [],
   'expose-functions' => [],
   'expose-constants' => [],
];

Thank in advance for your help?

hugofintecture avatar Mar 02 '22 10:03 hugofintecture

Do I understand correctly that if you check this file, simply by removing __ from the excluded functions you get the result you want (for this file)?

If so could you provide me the exact file in question?

theofidry avatar Mar 06 '22 14:03 theofidry

Yes exactly.

Here's the original file from the library php-http/guzzle6-adapter:

Client.php
<?php

declare(strict_types=1);

namespace Http\Adapter\Guzzle6;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* HTTP Adapter for Guzzle 6.
*
* @author David de Boer <[email protected]>
*/
final class Client implements HttpClient, HttpAsyncClient
{
   /**
    * @var ClientInterface
    */
   private $client;

   /**
    * If you pass a Guzzle instance as $client, make sure to configure Guzzle to not
    * throw exceptions on HTTP error status codes, or this adapter will violate PSR-18.
    * See also self::buildClient at the bottom of this class.
    */
   public function __construct(?ClientInterface $client = null)
   {
       if (!$client) {
           $client = self::buildClient();
       }

       $this->client = $client;
   }

   /**
    * Factory method to create the Guzzle 6 adapter with custom Guzzle configuration.
    */
   public static function createWithConfig(array $config): Client
   {
       return new self(self::buildClient($config));
   }

   /**
    * {@inheritdoc}
    */
   public function sendRequest(RequestInterface $request): ResponseInterface
   {
       $promise = $this->sendAsyncRequest($request);

       return $promise->wait();
   }

   /**
    * {@inheritdoc}
    */
   public function sendAsyncRequest(RequestInterface $request)
   {
       $promise = $this->client->sendAsync($request);

       return new Promise($promise, $request);
   }

   /**
    * Build the Guzzle client instance.
    */
   private static function buildClient(array $config = []): GuzzleClient
   {
       $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler());
       $handlerStack->push(Middleware::prepareBody(), 'prepare_body');
       $config = array_merge(['handler' => $handlerStack], $config);

       return new GuzzleClient($config);
   }
}

And here's the scoped file:

Client.php (scoped)
<?php

declare (strict_types=1);
namespace FintectureWC\Http\Adapter\Guzzle6;

use FintectureWC\GuzzleHttp\Client as GuzzleClient;
use FintectureWC\GuzzleHttp\ClientInterface;
use FintectureWC\GuzzleHttp\HandlerStack;
use FintectureWC\GuzzleHttp\Middleware;
use FintectureWC\Http\Client\HttpAsyncClient;
use FintectureWC\Http\Client\HttpClient;
use FintectureWC\Psr\Http\Message\RequestInterface;
use FintectureWC\Psr\Http\Message\ResponseInterface;
/**
* HTTP Adapter for Guzzle 6.
*
* @author David de Boer <[email protected]>
*/
final class Client implements HttpClient, HttpAsyncClient
{
   /**
    * @var ClientInterface
    */
   private $client;
   /**
    * If you pass a Guzzle instance as $client, make sure to configure Guzzle to not
    * throw exceptions on HTTP error status codes, or this adapter will violate PSR-18.
    * See also self::buildClient at the bottom of this class.
    */
   public function __construct(?ClientInterface $client = null)
   {
       if (!$client) {
           $client = self::buildClient();
       }
       $this->client = $client;
   }
   /**
    * Factory method to create the Guzzle 6 adapter with custom Guzzle configuration.
    */
   public static function createWithConfig(array $config) : Client
   {
       return new self(self::buildClient($config));
   }
   /**
    * {@inheritdoc}
    */
   public function sendRequest(RequestInterface $request) : ResponseInterface
   {
       $promise = $this->sendAsyncRequest($request);
       return $promise->wait();
   }
   /**
    * {@inheritdoc}
    */
   public function sendAsyncRequest(RequestInterface $request)
   {
       $promise = $this->client->sendAsync($request);
       return new Promise($promise, $request);
   }
   /**
    * Build the Guzzle client instance.
    */
   private static function buildClient(array $config = []) : GuzzleClient
   {
       $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler());
       $handlerStack->push(Middleware::prepareBody(), 'prepare_body');
       $config = \array_merge(['handler' => $handlerStack], $config);
       return new GuzzleClient($config);
   }
} 

hugofintecture avatar Mar 07 '22 08:03 hugofintecture

I'm having the same issue. The issue is that when "__" is added to the exclude-functions array, then functions are no longer exposed, even if you add them to the expose-functions array.

I got it working by adding it as a regex instead like "/__/". You can try that as a workaround if it works for you until this gets fixed.

codealfa avatar Apr 06 '22 01:04 codealfa

I'm having the same issue. The issue is that when "__" is added to the exclude-functions array, then functions are no longer exposed, even if you add them to the expose-functions array.

I got it working by adding it as a regex instead like "/__/". You can try that as a workaround if it works for you until this gets fixed.

Works great @codealfa thanks!

hugofintecture avatar Apr 15 '22 12:04 hugofintecture

Closed via #687 thanks to @bobab12

theofidry avatar Oct 12 '22 13:10 theofidry