I have a problem with an autocompletion in PhpStorm...
class Main
{
use Something;
/**
* #var SplObjectStorage
*/
private $container;
public function __construct()
{
$this->container = new SplObjectStorage();
}
public function addSth()
{
$this->add();
}
}
trait Something
{
public function add()
{
$this->container->attach(new stdClass());
}
}
$m = new Main();
$m->add();
var_dump($m);
Everything works fine but $this->container->attach(new stdClass()); throws that method attach is not found... Anyone can help? I think that properly configured PHPDoc should help.
The Trait has no way of knowing what type its $container is. In your example it is SplObjectStorage, but what if it isn't?
You need to place $container inside the Trait as well and declare it as SplObjectStorage. Then it should work. This way, you'll also be sure that whoever is declaring that Trait actually has a $container to have it work on.
trait Something {
/**
* #var SplObjectStorage
*/
private $container;
...
I suppose you can force the issue:
public function add()
{
/**
* #var $cont SplObjectStorage
*/
$cont = $this->container;
$cont->attach(new stdClass());
}
There's a couple of other ways to make this work.
Define $container inside the trait (as #Iserni suggested), but define the variable itself. This actually makes more sense to define it within the trait anyways, since the trait methods actually rely on it.
trait Something {
/** #var \SplObjectStorage */
protected $container;
public function add() {
$this->container->attach(new stdClass());
}
}
Pass it as an argument in your function
public function add(\SplObjectStorage $container) {
$container->attach(new stdClass());
}
PHPStorm has to have a way to refer back to the class to do things like autocomplete. Your trait cannot inherit its docs from the calling class. Your class, however, can inherit docs from the included trait.
Related
I wondered why it is possible to access to the private properties and methods of a PHP Trait in a class that uses that Trait.
Here is my PHP Trait: BulkMessageMembers.php:
<?php
namespace App\Traits;
trait BulkMessageMembers
{
private string $endpoint;
private string $text;
private array $phone_numbers;
private string $server_response;
public bool $sms_status = false;
public int $user_id;
}
And my PHP class (This is only a portion of code.) SMSJsonBulkMessageService.php that uses my Trait:
<?php
namespace App\Services;
use App\Interfaces\SMSGateway;
use App\Models\Balance;
use App\Models\Message;
use App\Models\MessageSetting;
use App\Models\UserMessageSetting;
use App\Traits\BulkMessageMembers;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Support\Collection;
class SMSJsonBulkMessageService implements SMSGateway
{
use BulkMessageMembers;
public function __construct(
private readonly UserMessageSetting $preference,
private readonly Collection $messages, private readonly MessageSetting $setting
)
{
$this->prepare();
}
private function prepare(): void
{
$this->endpoint = $this->preference->messageProvider->bulk_endpoint;
$this->messages->each(function ($message) {
$this->phone_numbers[] = sprintf('243%d', $message->phone_number);
});
$this->text = $this->messages->first()->description;
}
/**
* #throws ConnectException
* #return void
*/
public function send(): void
{
//
}
public function update(): void
{
//
}
}
Can someone explain to me why from my PHP class I access the private properties of a Trait? I know trait is not a class per se, but its main interest is to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
Need clarification please.
A common explanation of traits is that they are "compiler-assisted copy-and-paste": when you incorporate a trait into a concrete class, the contents of that trait are inserted into the class definition as though you had written them by hand.
No distinction is maintained between members inserted by the trait, and members written directly in the class definition; and no relationship is maintained between the class and the trait the members were copied from. In some cases, there are additional constraints checked by the compiler, or renaming that is performed as part of the use statement, but in the simple case, it is exactly as though you had copied the code from one file to the other.
In your case, the code that is compiled is essentially this:
<?php
namespace App\Services;
use App\Interfaces\SMSGateway;
use App\Models\Balance;
use App\Models\Message;
use App\Models\MessageSetting;
use App\Models\UserMessageSetting;
use App\Traits\BulkMessageMembers;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Support\Collection;
class SMSJsonBulkMessageService implements SMSGateway
{
private string $endpoint;
private string $text;
private array $phone_numbers;
private string $server_response;
public bool $sms_status = false;
public int $user_id;
public function __construct(
private readonly UserMessageSetting $preference,
private readonly Collection $messages, private readonly MessageSetting $setting
)
{
$this->prepare();
}
private function prepare(): void
{
$this->endpoint = $this->preference->messageProvider->bulk_endpoint;
$this->messages->each(function ($message) {
$this->phone_numbers[] = sprintf('243%d', $message->phone_number);
});
$this->text = $this->messages->first()->description;
}
/**
* #throws ConnectException
* #return void
*/
public function send(): void
{
//
}
public function update(): void
{
//
}
}
Looking at that code, it should be no surprise that the prepare method has access to $this->phone_numbers.
Beyond #IMSoP's great answer, I think quoting the RFC that introduced the feature brings clarity, specifically the last sentence:
The Flattening Property
As already mentioned, multiple inheritance and Mixins are complex mechanisms. Traits are an alternative which have been designed to impose no additional semantics on classes. Traits are only entities of the literal code written in your source files. There is no notion about Traits at runtime. They are used to group methods and reuse code and are totally flattened into the classes composed from them. It is almost like a language supported and failsafe copy'n'paste mechanism to build classes.
I am trying to document my code and to remove my IDE warnings on my code. I'm working with IntelliJ IDEA.
I have two classes, the first is an abstract Controller like this :
abstract class Controller {
/** #var Service $service */
public static $service;
...
}
The second one is a UserController, which extends the first :
/** #property UserService $service */
abstract class UserController extends Controller {
public static function testService() {
static::$service->testMethod();
}
...
}
My issues :
If I declare a new function on the UserService without the #property tag, my IDE is telling me that Method 'testMethod' not found in Service
If I use the #property, the IDE is telling me that Field '$service' not found in UserController
How can I fix this ?
I would solve the need via a property override... this shouldn't hinder the code logic itself:
abstract class Controller {
/** #var Service */
public static $service;
...
}
abstract class UserController extends Controller {
/** #var UserService */
public static $service;
public static function testService() {
static::$service->testMethod();
}
...
}
Side note: the #var syntax for class properties doesn't need the variable name repeated... that's only needed when #var is used to denote type on a local variable. In the class property case, including the property name is actually just using the property name as the first word of the "description" portion.
base classes:
abstract class A
{
public function methodA() { echo __FUNCTION__.'<br>'; }
}
class B extends A
{
public function methodB() { echo __FUNCTION__.'<br>'; }
}
so far so good.
A factory which can create any of them:
class Factory
{
/**
* #return A
*/
public static function createAorB()
{
return new B();
}
}
phpdoc is right, since it say "return something comes from A".
Now the tricky part comes:
class OwnClass
{
/**
* #var A
*/
protected $var;
public function __construct()
{
$this->var = \Factory::createAorB();
}
}
class OwnClassB extends OwnClass
{
public function callMe()
{
$this->var->methodA();
$this->var->methodB(); // this will also run, but IDE wont autocomplete with this method
}
}
(new OwnClassB())->callMe();
this will run for sure, but check for the methodB() calling. IDE wont autocomplete with this method. How to tell that var is now B, not A with phpDoc?
You can override inherited properties for PHPStorm's autocompletion:
/**
* #property B $var
*/
class OwnClassB extends OwnClass
{
//...
}
However, I personally think you're trying to solve the wrong problem. If you have a Factory class that returns either A or B, you shouldn't type hint the return value to A. Instead, you should provide both:
class Factory
{
/**
* #return A|B
*/
public static function createAorB()
{
return new B();
}
}
Both options inform PHPStorm enough to let it show you both methods as autocomplete options, but the latter is accurate in terms of what the code actually does (well, not in this example, but given the function name I guess that's what your real-world implementation does).
I want to know if there is a solution on how to unit-test a PHP trait.
I know we can test a class which is using the trait, but I was wondering if there are better approaches.
Thanks for any advice in advance :)
EDIT
One alternative is to use the Trait in the test class itself as I'm going to demonstrate bellow.
But I'm not that keen on this approach since there is no guaranty there are no similar method names between the trait, the class and also the PHPUnit_Framework_TestCase (in this example):
Here is an example trait:
trait IndexableTrait
{
/** #var int */
private $index;
/**
* #param $index
* #return $this
* #throw \InvalidArgumentException
*/
public function setIndex($index)
{
if (false === filter_var($index, FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException('$index must be integer.');
}
$this->index = $index;
return $this;
}
/**
* #return int|null
*/
public function getIndex()
{
return $this->index;
}
}
and its test:
class TheAboveTraitTest extends \PHPUnit_Framework_TestCase
{
use TheAboveTrait;
public function test_indexSetterAndGetter()
{
$this->setIndex(123);
$this->assertEquals(123, $this->getIndex());
}
public function test_indexIntValidation()
{
$this->setExpectedException(\Exception::class, '$index must be integer.');
$this->setIndex('bad index');
}
}
You can test a Trait using a similar to testing an Abstract Class' concrete methods.
PHPUnit has a method getMockForTrait which will return an object that uses the trait. Then you can test the traits functions.
Here is the example from the documentation:
<?php
trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class TraitClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');
$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->concreteMethod());
}
}
?>
You can also use getObjectForTrait , then assert the actual result if you want.
class YourTraitTest extends TestCase
{
public function testGetQueueConfigFactoryWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$config = $obj->getQueueConfigFactory();
$this->assertInstanceOf(QueueConfigFactory::class, $config);
}
public function testGetQueueServiceWithoutInstanceWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$service = $obj->getQueueService();
$this->assertInstanceOf(QueueService::class, $service);
}
}
Since PHP 7 we can now use annonymous classes...
$class = new class {
use TheTraitToTest;
};
// We now have everything available to test using $class
I have trouble with dependencies in my application in service layer.
I have following class:
<?php
class UserService{
private $userRepository;
private $vocationService;
private $roleService;
public function __construct(UserRepository $userRepository, VocationService $vocationService, RoleService $roleService)
{
$this->userRepository = $userRepository;
$this->vocationService = $vocationService;
$this->roleService = $roleService;
}
}
There are only three dependencies which I'm injecting.
Assume, I want to add next dependency, for example: NextService.
My constructor will grow again.
What if I wanted to pass more dependencies within constructor ?
Maybe should I solve this problem by passing IoC container and then get desirable class?
Here is an example:
<?php
class UserService{
private $userRepository;
private $vocationService;
private $roleService;
public function __construct(ContainerInterface $container)
{
$this->userRepository = $container->get('userRepo');
$this->vocationService = $container->get('vocService');
$this->roleService = $container->get('roleService');
}
}
But now my UserService class depends on IoC container which I'm injecting.
How to solve a problem following good practices?
Regards, Adam
Injecting the container as a dependency to your service is considered as a bad practice for multiple reasons. I think the main point here is to figure out why and then try to understand the problem that leads you to think about "injecting the container" as a possible solution and how to solve this problem.
In object oriented programming, it's important to clearly define the relations between objects. When you're looking at a given object dependencies, it should be intuitive to understand how the object behaves and what are the other objects it relies on by looking at its public API.
It's also a bad idea to let your object rely on a dependency resolver, In the example you shared your object can't live without the container which is provided by the DI component.
If you want to use that object elsewhere, in an application that uses another framework for example, you'll then have to rethink the way your object get its dependencies and refactor it.
The main problem here is to understand why your service needs all these dependencies,
In object-oriented programming, the single responsibility principle
states that every context (class, function, variable, etc.) should
define a single responsibility, and that responsibility should be
entirely encapsulated by the context. All its services should be
narrowly aligned with that responsibility.
Source: Wikipedia
Based on this definition, I think you should split your UserService into services that handle only one responsability each.
A service that fetch users and save them to your dababase for example
Another service that manages roles for example
... and so on
I agree that __construct can grow fairly easy.
However, you have a Setter DI at your disposal: http://symfony.com/doc/current/components/dependency_injection/types.html#setter-injection
Morover, there is a Property DI, but I wouldn't recommed it as ti leaves your service wide-open to manipulation: http://symfony.com/doc/current/components/dependency_injection/types.html#property-injection
You can abstract some of the commonly used services in one helper service and then just inject this helper into your other services. Also you can define some useful functions in this helper service. Something like this:
<?php
namespace Saman\Library\Service;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Form\FormFactory;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
use Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityManager;
class HelperService
{
protected $translator;
protected $securityContext;
protected $router;
protected $templating;
protected $em;
public function __construct(
Translator $translator,
SecurityContext $securityContext,
Router $router,
TimedTwigEngine $templating,
EntityManager $em
)
{
$this->translator = $translator;
$this->securityContext = $securityContext;
$this->router = $router;
$this->templating = $templating;
$this->em = $em;
}
Getters ...
public function setParametrs($parameters)
{
if (null !== $parameters) {
$this->parameters = array_merge($this->parameters, $parameters);
}
return $this;
}
/**
* Get a parameter from $parameters array
*/
public function getParameter($parameterKey, $defaultValue = null)
{
if (array_key_exists($parameterKey, $this->parameters)) {
return $this->parameters[$parameterKey];
}
return $defaultValue;
}
}
Now imagine you have a UserService then you define it like this:
<?php
namespace Saman\UserBundle\Service;
use Saman\Library\Service\HelperService;
class UserService
{
protected $helper;
public function __construct(
Helper $helper,
$parameters
)
{
$this->helper = $helper;
$this->helper->setParametrs($parameters);
}
public function getUser($userId)
{
$em = $this->helper->getEntityManager();
$param1 = $this->helper->getParameter('param1');
...
}
This example was created for Symfony 4 but the principle should work in older versions.
As others have mentioned, it's good to engineer your application to limit the functional scope of each service and reduce the number of injections on each consuming class.
The following approach will help if you truely need many injections, but it's also a nice tidy way to reduce boilerplate if you are injecting a service in many places.
Consider a service App\Services\MyService that you wish to inject into App\Controller\MyController:
Create an 'Injector' trait for your service.
<?php
// App\Services\MyService
namespace App\DependencyInjection;
use App\Services\MyService;
trait InjectsMyService
{
/** #var MyService */
protected $myService;
/**
* #param MyService $myService
* #required
*/
public function setMyService(MyService $myService): void
{
$this->myService = $myService;
}
}
Inside your controller:
<?php
namespace App\Controller;
class MyController
{
use App\DependencyInjection\InjectsMyService;
...
public myAction()
{
$this->myService->myServiceMethod();
...
}
...
}
In this way:
a single line of code will make your service available in any container managed class which is super handy if you're using a service in many places
it's easy to search for your injector class to find all usages of a service
there are no magic methods involved
your IDE will be able to auto-complete your protected service instance property and know it's type
controller method signatures become simpler, containing only arguments
If you have many injections:
<?php
namespace App\Controller;
use App\DependencyInjection as DI;
class SomeOtherController
{
use DI\InjectsMyService;
use DI\InjectsMyOtherService;
...
use DI\InjectsMyOtherOtherService;
...
}
You can also create an injector for framework provided services, e.g. the doctrine entity manager:
<?php
namespace App\DependencyInjection;
use Doctrine\ORM\EntityManagerInterface;
trait InjectsEntityManager
{
/** #var EntityManagerInterface */
protected $em;
/**
* #param EntityManagerInterface $em
* #required
*/
public function setEm(EntityManagerInterface $em): void
{
$this->em = $em;
}
}
class MyClass
{
...
use App\DependencyInjection\InjectsEntityManager;
A final note: I personally wouldn't try to make these injectors any smarter than what I've outlined. Trying to make a single polymorphic injector will probably obfuscate your code and limit your IDE's ability to auto-complete and know what type your services are.