jmikola/WildcardEventDispatcher

Warning: preg_match(): Compilation failed: unmatched closing parenthesis at offset 32 in /app/symfony/vendor/jmikola/wildcard-event-dispatcher/src/Jmikola/WildcardEventDispatcher/ListenerPattern.php on line 102

Closed this issue · 10 comments

I get this error when I deploy application on heroku

Warning: preg_match(): Compilation failed: unmatched closing parenthesis at offset 32 in /app/symfony/vendor/jmikola/wildcard-event-dispatcher/src/Jmikola/WildcardEventDispatcher/ListenerPattern.php on line 102

I am using Symfony 3.4

Can you create a failing test case (see: ListenerPatternTest.php) to reproduce this? Alternatively, knowing the original arguments used to construct the ListenerPattern leading up to this error might also help.

AFAIK, this error has not been reported previously -- and given that we do have test coverage for the class in question, I wouldn't think this is caused by the regex patterns defined in ListenerPattern::$replacements. The only other variable for compiling the regex comes from the event pattern used to construct the class, which is the main variable in the test cases I linked above.

I am not sure how to create a failing test case, never used unit tests before. Also it is confusing what data I should use in providePatternsAndMatches method, apparently it returns array of arrays containing pattern and matching events, not sure about second element and third element of array, what it is supposed to match, for example this image

image

The wildcard we used in our app was qbil.trade.# and it was supposed to match events like qbil.trade.order.created, qbil.trade.order.cancelled, qbil.trade.purchase.received_quantity_changed, etc

providePatternsAndMatches() is a data provider for ListenerPatternTest::testPatternMatching(). The test method expects three parameters:

  • string $eventPattern
  • string[] $expectedMatches
  • string[] $expectedMisses

The test itself constructs a ListenerPattern from the pattern string and then iterates on each of the other array arguments. Each string in $expectedMatches and $expectedMisses must match and miss the pattern, respectively.

I tested your example quickly by adding the following to the data provider:

[
    'qbil.trade.#',
    ['qbil.trade.order.created', 'qbil.trade.order.cancelled', 'qbil.trade.purchase.received_quantity_changed'],
    // re-using some misses from previous test cases, as none of these should match your pattern anyway
    ['api', 'api.user', 'core.api.post.created'],
],

I then ran the test with vendor/bin/phpunit --filter ListenerPatternTest and it passed as expected. I'm not able to reproduce the original error your reported. By default, Composer pulled in symfony/event-dispatcher 4.2.3, but I also forced it to install 3.4.22 to corresponding with the version of Symfony you're using.

Unless you can debug the pattern being used to construct ListenerPatternTest immediately before the preg_match() warning is raised, I don't think we'll be able to get to the bottom of this. My assumption is that qbil.trade.# isn't actually being passed to the constructor at runtime as you expect.

Unless you can debug the pattern being used to construct ListenerPatternTest immediately before the preg_match() warning is raised, I don't think we'll be able to get to the bottom of this.

I got this error only in heroku production, couldn't get a repro in dev env (making it difficult to debug), neither in other prod envs which run on our own servers.

My assumption is that qbil.trade.# isn't actually being passed to the constructor at runtime as you expect.

Could be, but I am not sure why I couldn't get a repro in other environments (except one heroku prod env).

You may close the issue if u want, I think I can't give you more info

The only other thing that comes to mind is that if qbil.trade.# is being passed along through YAML or some other format, the trailing # (and any possibly characters to the right of it) may be cut off as a comment initializer. If so, that might be rectified by ensuring the string is quoted.

That said, I did test constructing a ListenerPattern with qbil.trade. earlier when I first thought of that possibility, but the error message was quite different.

If this comes up again and you find yourself able to reproduce, feel free to follow up here or open a new issue.

@jmikola

Received this error in development env too with PHP 7.3

image

The error is most certainly because of the backslash before the first ( character in the pattern, which escapes the ( and leads to an unmatched ) later in the pattern. To reproduce:

preg_match('/^qbil\.trade\.\(?:|\w+(?:\.\w+)*)$/', 'kernel.exception');

If we walk through the constructor, the event pattern is passed to createRegex(), escaped using preg_quote(), and then we use preset search/replace patterns to convert special sequences of characters per AMQP's wildcard syntax (as described in the README). Since (?:|\w+(?:\.\w+)*) is the only special replacement pattern present in the final string (i.e. used in preg_match()), I would assume that the original input to ListenerPattern ends with a # character that is not preceded by a . character (i.e. "Multi-wildcard without separator prefix").

Let's work backwards from the final string of '/^qbil\.trade\.\(?:|\w+(?:\.\w+)*)$/ based on the steps in createRegex().

  • Unwrap the characters added by sprintf() and we get qbil\.trade\.\(?:|\w+(?:\.\w+)*)
  • Undo the "Multi-wildcard without separator prefix" replacement and we get qbil\.trade\.\#
  • Determine an input string for preg_quote() that would have produced qbil\.trade\.\#

From what I recall, preg_quote() would escape . characters but not #; however, the error you're experiencing would have required # to also have been escaped in order to introduce \# into the string. This lead me to look at preg_quote() and it appears that PHP 7.3.0 started escaping #, which explains the error.

I think this can be quickly fixed by adding a check of the PHP version, so we can properly infer whether # will be escaped in the quoted string (and adjust the search/replace patterns accordingly). I'll re-open this and follow up with a PR and release.

Thanks for following up when the error popped up a second time.


Also, here are two things to keep in mind for future bug reports (which could have saved some time here):

  • Eagerly provide details about your runtime environment. You noted Symfony 3.4 above, but you didn't mention your PHP version, the version of this package, etc. Even Symfony 3.4 is a bit vague (I assume you were using a patch release and not 3.4.0), although it wouldn't have made a difference here.
  • Provide the original raw text for the error/stacktrace rather than posting a screenshot (or do both). Markdown's code-formatting blocks (three back-ticks before and after) works well for sharing such text. That would have made it much easier to copy/paste the exact pattern instead of transcribing it from the image. While it wasn't a factor here, transcribing generally makes it harder to be certain that the text was accurately copied as different characters could appear visually similar.

Fixed in 1.1.2.

@jmikola

Packagist still shows old package and as such composer update isn't updating the package automatically

https://packagist.org/packages/jmikola/wildcard-event-dispatcher-bundle

The package for the library (this repository) is jmikola/wildcard-event-dispatcher and it looks like Composer is aware of the 1.1.2 tag. The latest release of the bundle (1.0.3) uses ^1.1.0 for its library dependency, so upgrading packages should certainly pick up on the change.

Note that the latest version of the bundle does not support Symfony 4 (see: jmikola/JmikolaWildcardEventDispatcherBundle#7); however, I don't believe that affects you if you're still on Symfony 3.4.