Sim
Well-known member
- Affected version
- 2.3.7
Okay - so here's a weird edge case I haven't been able to work out yet.
I'm building an addon. This addon uses a composer package. The composer package has a trait. I want to "use" that trait from the composer package in one of my commands.
Adding the "use" clause causes the XenForo command runner to fail catastrophically (as in, you can't run any commands at all), with an error message saying that the trait was not found.
Using the same trait from the same composer package in a controller works exactly as you would expect - no problems. So it's something specific to the Cli environment.
I've created an example composer package and a XenForo addon to demonstrate this issue.
DO NOT INSTALL THIS XENFORO ADDON ON A PRODUCTION SITE.
The package is: somevendor/traiter (namespace
The addon is: traiter-xenforo (namespace
The package has some traits and some classes, each of which simply echo out a string about which class or trait things are coming from.
The addon had two functions:
... with the important part being the controller. I've simplified the code below to focus on the important part - the
So that all works as expected - we can "use" the trait and then interact with it directly as if it were a part of our class.
So now let's look at the Cli command instead:
... again, I've simplified the above code. The code included in the package executes and outputs some information about various other traits and classes we've included for testing purposes.
But if you uncomment the
Now if you look at the code for the Cli command in more detail, there are alternatives I've included as examples that work:
If we have our own trait as a part of the addon, not from a composer package, it works fine:
That part works fine - so it can find and use traits - just not when they are loaded from composer packages.
We can also instantiate a new anonymous class and use our composer package trait just fine - but that kind of defeats the purpose of using a trait directly:
We can even use our trait from the composer package inside another class that we then use in the Cli app - but again, that's not what we're trying to achieve - we want to use a trait directly in our Command class.
Note that I also tried editing the base
It's clear that the composer autoloader works just fine - we can use the trait in other classes and in our controller, just not directly in a Cli application.
Now, what I haven't yet done is tested this in a stand-alone Symfony Cli application, or in a Laravel application (which also uses the Symfony console library) to see if the same issue exists.
I'm building an addon. This addon uses a composer package. The composer package has a trait. I want to "use" that trait from the composer package in one of my commands.
Adding the "use" clause causes the XenForo command runner to fail catastrophically (as in, you can't run any commands at all), with an error message saying that the trait was not found.
Using the same trait from the same composer package in a controller works exactly as you would expect - no problems. So it's something specific to the Cli environment.
I've created an example composer package and a XenForo addon to demonstrate this issue.
DO NOT INSTALL THIS XENFORO ADDON ON A PRODUCTION SITE.
The package is: somevendor/traiter (namespace
SomeVendor\Traiter
)The addon is: traiter-xenforo (namespace
SomeVendor\TraiterAddon
) ... and I've also attached the built addon as a zip.The package has some traits and some classes, each of which simply echo out a string about which class or trait things are coming from.
The addon had two functions:
- display output from the package in a public route
<boardUrl>/traiter
- display output from the package in a Cli app:
php cmd.php traiter
/traiter
URL in your board with the addon installed, you should receive output similar to this:
Code:
[Inside SomeVendor\Traiter\SomeClass] called from [SomeVendor\Traiter\SomeClass]
[Inside SomeVendor\Traiter\AbstractClass] called from [SomeVendor\Traiter\SomeClass]
[Inside SomeVendor\Traiter\Traits\TraitA] called from [SomeVendor\Traiter\SomeClass]
[Inside SomeVendor\Traiter\Traits\TraitB] called from [SomeVendor\Traiter\SomeClass]
[Inside SomeVendor\Traiter\Traits\TraitC] called from [SomeVendor\TraiterAddon\Pub\Controller\Traiter]
[Inside SomeVendor\TraiterAddon\Traits\TraitD] called from [SomeVendor\TraiterAddon\Pub\Controller\Traiter]
[Inside SomeVendor\TraiterAddon\Tools\AnotherClass] called from [SomeVendor\TraiterAddon\Tools\AnotherClass]
... with the important part being the controller. I've simplified the code below to focus on the important part - the
use TraitC;
clause - TraitC comes from the composer package. See the Git repo or the addon contents for the full class:
PHP:
<?php namespace SomeVendor\TraiterAddon\Pub\Controller;
use SomeVendor\Traiter\Traits\TraitC;
...
class Traiter extends AbstractController
{
use TraitC;
public function actionIndex(ParameterBag $params)
{
...
$this->echoFromTraitC();
...
}
}
So that all works as expected - we can "use" the trait and then interact with it directly as if it were a part of our class.
So now let's look at the Cli command instead:
PHP:
<?php namespace SomeVendor\TraiterAddon\Cli\Command;
use SomeVendor\Traiter\Traits\TraitC;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use XF\Cli\Command\AbstractCommand;
class Traiter extends AbstractCommand
{
// if we uncomment the following line, it not only breaks this command, but all XenForo CLI commands
// use TraitC;
protected function configure()
{
$this
->setName('traiter')
->setDescription('Test traits in composer packages');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
// can't use this unless we can use our TraitC
// $this->echoFromTraitC();
...
return self::SUCCESS;
}
}
... again, I've simplified the above code. The code included in the package executes and outputs some information about various other traits and classes we've included for testing purposes.
But if you uncomment the
use TraitC;
clause, it will break the entire XF Cli runner with a fatal error message about that trait not being found:PHP Fatal error: Trait "SomeVendor\Traiter\Traits\TraitC" not found ...
Now if you look at the code for the Cli command in more detail, there are alternatives I've included as examples that work:
If we have our own trait as a part of the addon, not from a composer package, it works fine:
PHP:
<?php namespace SomeVendor\TraiterAddon\Cli\Command;
use SomeVendor\TraiterAddon\Traits\TraitD;
class Traiter extends AbstractCommand
{
// this trait is from our addon, not from the composer package
use TraitD;
protected function execute(InputInterface $input, OutputInterface $output)
{
...
$this->echoFromTraitD();
...
return self::SUCCESS;
}
}
That part works fine - so it can find and use traits - just not when they are loaded from composer packages.
We can also instantiate a new anonymous class and use our composer package trait just fine - but that kind of defeats the purpose of using a trait directly:
PHP:
// but we can do this - it just defeats the purpose of using a trait at the class level
$bar = (new class { use TraitC; });
$bar->echoFromTraitC();
We can even use our trait from the composer package inside another class that we then use in the Cli app - but again, that's not what we're trying to achieve - we want to use a trait directly in our Command class.
PHP:
<?php namespace SomeVendor\TraiterAddon\Tools;
use SomeVendor\Traiter\Traits\TraitC;
class AnotherClass
{
// this works fine!
use TraitC;
public function echoFromAnotherClass()
{
echo "[Inside " . AnotherClass::class . "] called from [" . get_class($this) . "]\n";
}
}
Note that I also tried editing the base
Symfony\Component\Console\Command\Command
class and using the trait at that level, but it causes the same "Trait not found" error, so it's something fundamental to the way the Cli runner operates.It's clear that the composer autoloader works just fine - we can use the trait in other classes and in our controller, just not directly in a Cli application.
Now, what I haven't yet done is tested this in a stand-alone Symfony Cli application, or in a Laravel application (which also uses the Symfony console library) to see if the same issue exists.
Attachments
Last edited: