As more and more service oriented PHP is being done these days, I find myself with a lot of PHP files that have this long chunk of code just for initialization. In other languages like C++ or typescript you can do the following without all the duplication. A PHP example:
namespace Test\Controllers;
use Test\Services\AppleService;
use Test\Services\BananaService;
use Test\Services\PearService;
use Test\Services\LemonService;
use Test\Services\PeachService;
class TestController{
protected $appleService;
protected $bananaService;
protected $lemonService;
protected $pearService;
protected $peachService;
public function __construct(
AppleService $appleService,
BananaService $bananaService,
LemonService $lemonService,
PearService $pearService,
PeachService $peachService
){
$this->appleService = $appleService;
$this->bananaService = $bananaService;
$this->lemonService = $lemonService;
$this->pearService = $pearService;
$this->peachService = $peachService;
}
}
A Typescript Example:
module namespace Test.Controllers {
export class TestController{
constructor(
private appleService:Test.Services.AppleService,
private bananaService:Test.Services.BananaService,
private lemonService:Test.Services.LemonService,
private pearService:Test.Services.PearService,
private peachService:Test.Services.PeachService
){}
}
}
Are there better/shorter ways to do the same thing, and/or is any support to make this easier planned for upcoming PHP releases?
There is an alternative, property injection:
use DI\Annotation\Inject;
class TestController
{
/**
* #Inject
* #var AppleService
*/
private $appleService;
/**
* #Inject
* #var BananaService
*/
private $bananaService;
/**
* #Inject
* #var LemonService
*/
private $lemonService;
/**
* #Inject
* #var PearService
*/
private $pearService;
/**
* #Inject
* #var PeachService
*/
private $peachService;
}
Is that shorter, or simpler to write? I'll let you judge. But I love it, I don't end up with a bloated constructor. This is inspired from Java/Spring FYI.
Now this example works with PHP-DI (disclaimer: I work on that), but there may be other DI containers that offer the same functionality.
WARNING: property injection doesn't fit everywhere.
I find it appropriate for controllers (since controllers are not supposed to be reused). Read more here.
I don't think it would be appropriate to use it in services. So that's not the ultimate solution.
This syntactic sugar will finally arrive in PHP 8.0 (November 26, 2020).
By prefixing arguments in the contructor with public, protected or private, they will be copied to and defined as class properties with the same name.
In PHP 8.0 the example of OP can be written as:
namespace Test\Controllers;
use Test\Services\AppleService;
use Test\Services\BananaService;
use Test\Services\PearService;
use Test\Services\LemonService;
use Test\Services\PeachService;
class TestController{
public function __construct(
protected AppleService $appleService,
protected BananaService $bananaService,
protected LemonService $lemonService,
protected PearService $pearService,
protected PeachService $peachService
){
}
}
https://wiki.php.net/rfc/constructor_promotion
https://stitcher.io/blog/constructor-promotion-in-php-8
Related
Prerequisites:
PHP 7.1.8
Symfony 3.3.9
Doctrine 2.6.x-dev
I wonder if it's possible to override an inversedBy attribute of a property association mapping that's taken from a trait.
An interface that I use as a concrete user entity placeholder:
ReusableBundle\ModelEntrantInterface.php
interface EntrantInterface
{
public function getEmail();
public function getFirstName();
public function getLastName();
}
The following architecture works just fine (need to create User entity that implements EntrantInterface and all other entities that are derived from these abstract classes in AppBundle):
ReusableBundle\Entity\Entry.php
/**
* #ORM\MappedSuperclass
*/
abstract class Entry
{
/**
* #var EntrantInterface
*
* #ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="entries")
* #ORM\JoinColumn(name="user_id")
*/
protected $user;
// getters/setters...
}
ReusableBundle\Entity\Timestamp.php
/**
* #ORM\MappedSuperclass
*/
abstract class Timestamp
{
/**
* #var EntrantInterface
*
* #ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface", inversedBy="timestamps")
* #ORM\JoinColumn(name="user_id")
*/
protected $user;
// getters/setters...
}
And couple more entities with similar structure that utilize EntranInterface
And this is what I want to achieve - UserAwareTrait to be reusable across several entities:
ReusableBundle\Entity\Traits\UserAwareTrait.php
trait UserAwareTrait
{
/**
* #var EntrantInterface
*
* #ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface")
* #ORM\JoinColumn(name="user_id")
*/
protected $user;
// getter/setter...
}
In Doctrine 2.6 if I would use super class and wanted to override its property I'd do this:
/**
* #ORM\MappedSuperclass
* #ORM\AssociationOverrides({
* #ORM\AssociationOverride({name="property", inversedBy="entities"})
* })
*/
abstract class Entity extends SuperEntity
{
// code...
}
But if I want that Entity to use UserAwareTrait and override association mapping of a property...
/**
* #ORM\MappedSuperclass
* #ORM\AssociationOverrides({
* #ORM\AssociationOverride({name="user", inversedBy="entries"})
* })
*/
abstract class Entry
{
use UserAwareTrait;
// code...
}
... and run php bin/console doctrine:schema:validate I see this error in the console:
[Doctrine\ORM\Mapping\MappingException]
Invalid field override named 'user' for class 'ReusableBundle\Entity\Entry'.
Is there a workaround that I could follow to achieve the desired result?
Use trait to store shared properties
Override assotiation mapping or (possibly) attributes mapping in the class that uses that trait
TL;DR You should change the access modificator from protected to private. Don't forget that you will not be able to directly manipulate the private property in a subclass and will need a getter.
The exception appears due to the bug (I believe, or a quirk of the behavior) in the AnnotationDriver.
foreach ($class->getProperties() as $property) {
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
||
...) {
continue;
}
It skips all non-private properties for MappedSuperclass letting them to compose metadata on the subclass parsing. But when it comes to overriding the driver tries to do it at a MappedSuperclass level, it doesn't remember that the property was skipped, fails to find it in the metadata and raise an exception.
I made a detailed explanation at the issue. You can find there also the link to the unit tests that highlight the case.
You'll have to try this in your own code to see, but it could be possible.
As an experiment, I overridden a trait in a class, then checked for the trait using class_uses() http://php.net/manual/en/function.class-uses.php
<?php
trait CanWhatever
{
public function doStuff()
{
return 'result!';
}
}
class X
{
use CanWhatever;
public function doStuff()
{
return 'overridden!';
}
}
$x = new X();
echo $x->doStuff();
echo "\n\$x has ";
echo (class_uses($x, 'CanWhatever')) ? 'the trait' : 'no trait';
This outputs:
overridden!
$x has the trait
Which you can see here https://3v4l.org/Vin2H
However, Doctrine Annotations may still pick up the DocBlock from the trait proper rather than the overridden method, which is why I can't give you a definitive answer. You just need to try it and see!
I had a similiar problem and solve it by override the property it self:
use UserAwareTrait;
/**
* #var EntrantInterface
* #ORM\ManyToOne(targetEntity="ReusableBundle\Model\EntrantInterface"inversedBy="entries")
*/
protected $user;
Hi Quick question about dependency injection
I'm using symfony 3 and am coming to terms with DI
Say I have a class
use Doctrine\ORM\EntityManagerInterface;
class CommonRepository
{
/**
* #var EntityManagerInterface
*/
protected $em;
/**
* DoctrineUserRepository constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntityManager()
{
return $this->em;
}
}
Now a new class called UserRepository and I inject the above class, does that mean I can access the injected items injected items (clearly he was dreaming at the end of inception)?
class UserRepository
{
/**
* #var CommonDoctrineRepository
*/
private $commonRepository;
/**
* #var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* DoctrineUserRepository constructor.
* #param CommonRepository $CommonRepository
*/
public function __construct(CommonRepository $CommonRepository)
{
$this->commonRepository = $commonRepository;
$this->em = $this->commonRepository->getEntityManager();
}
public function find($id)
{
//does not seem to work
//return $em->find($id);
//nor does
//return $this->em->find($id);
}
}
even if I extend the class and then try to construct parent no joy, clearly I can just inject the Doctrine manager
into the UserRepository, I was just trying to get some understanding around DI and inheritance
class UserRepository extends CommonRepository
{
/**
* #var CommonDoctrineRepository
*/
private $commonRepository;
/**
* #var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
public function __construct(CommonDoctrineRepository $commonRepository, $em)
{
parent::__construct($em);
$this->commonRepository = $commonRepository;
}
}
for the symfony component I have defined services like
app.repository.common_repository:
class: AppBundle\Repository\Doctrine\CommonRepository
arguments:
- "#doctrine.orm.entity_manager"
app.repository.user_repository:
class: AppBundle\Repository\Doctrine\UserRepository
arguments:
- "#app.repository.common_repository"
Dependency Injection is just the process of passing objects into the constructor (or setter methods). A Dependency Injection Container (or Service Container) is nothing more but a helper to inject the correct instances of objects into the constructor.
With that said, it's clear that Dependency Injection doesn't influence PHP in any way (btw, nothing in Symfony does, it's all just plain PHP stuff).
So inheritance works just as it works with some plain PHP objects (which is a strange comparisation, as they already are plain PHP objects).
This means that if CommonRepository#getEntityManager() is public and the CommonRepository is instantiated correctly, this method should return the entity manager that was passed to its constructor.
The same applies to UserRepository: If you save the passed CommonRepository#getEntityManager() instance in the $em property, all methods can access the entity manager using this $em property. This means doing $this->em->find(...) should perfectly work ($em->find(...) shouldn't, as there is no $em variable).
tl;dr: The code you showed in your question (except from the weird extence example) works perfectly.
Our classes don't have explictic getPropertyName() and setPropertyName() methods. Instead we're using trait, which implements combination of magic __get(), __set() and __call() methods, so, we can have following as result:
class Device {
use ModelAttributes;
/**
* #var \string
*/
protected $title;
/**
* #var \string
*/
protected $subtitle;
}
$device = new Device();
$device->setTitle('PC');
$device->getTitle();
But the problem is, that now PhpStorm can't recognize and highlights these methods.
I know, that I can add #method string getTitle() into class phpDoc, but this will mean, taht I again need to add all get-ers and set-ers, but in form of phpDoc commetns, and I want to avoid this.
It would be really great, if I could do something like:
/**
* #var \string
* #get
* #set
*/
protected $title;
Does someone have experience with such situation?
i'm working with huge models on PHPStorm. I'm trying to minimize my annotations.
I want to turn this
Class Stack
{
/** #var string */
public $foo;
/** #var string */
public $bar;
/** #var int */
public $foobar;
}
into this:
Class Stack
{
/** #var string */ //for both vars
public $foo;
public $bar;
/** #var int */
public $foobar;
}
I found the ##+ syntax to define multiple vars but seems that is not working. Maybe there is a workaround?
Thank you very much.
By the way, can i tell phpstorm that
$this->MyModel is a MyModel type? Something like:
/** #var $this->MyModel MyModel **/
$this->MyModel
Because CodeIgniter puts all of your models inside a param of the controller.
I'm afraid that I've not seen any IDE that knows how to recognize the docblock template syntax of /*##+/.
As for $this->MyModel, you could try using the #property tag on that class where you are using $this->MyModel.
Although some IDEs can reportedly recognize that #var syntax to set a datatype on a local variable:
/** #var \MyModel $model */
$model = $this->MyModel;
I don't think it would work with a class property like that.
Why you cannot declare it with #property in the PHPDoc block of the class?
/**
* #property myModel MyModelClass
*/
class Test
{}
$t = new Test();
$t->myModel->[CTRL+Space will return available methods];
Anyway most better to declare property manually in the class. Just make it transparent.
It's really weird to use group comment for objects. I haven't had such practice.
But feel free to use similar code:
/**##+
* Test
*
* #var int
*/
protected $_aa = 1;
protected $_aa2 = 2;
/**##-*/
Is there a standard way to document the expected class of entities inside a Collection in the docblock comment in a Doctrine project? Something like:
/**
* #var Collection<User>
*/
protected $users;
Looks like PHPDoc is the de-facto standard for docblock annotations now, but I couldn't find any mention for this use case.
Here is a solution that enables you to have autocompletion both on the Collection methods and your objects methods:
/**
* #param Collection|User[] $users
*/
public function foo($users)
{
$users-> // autocompletion on Collection methods works
foreach ($users as $user) {
$user-> // autocompletion on User methods work
}
}
It works like a charm in PhpStorm at least.
UPDATE 2022
Doctrine now documents the generic types of Collection with the #template syntax, which is nowadays supported by PhpStorm & static analysis tools (Psalm & PHPStan) at least:
/**
* #var Collection<int, User>
*/
protected Collection $users;
The old PhpStorm hacky way of documenting this, Collection|User[] is no longer required.
There are a few different ways to document expected variables. Have a look at the phpDoc documentation for a full list of available tags.
class MyClass
{
/**
* Users collection
* #var \Doctrine\ORM\ArrayCollection
*/
protected $users;
/**
* My method that doesn't do much
* #param \Doctrine\ORM\ArrayCollection $users
* #return void
*/
public function myMethod(\Doctrine\ORM\ArrayCollection $users)
{
/** #var \Entities\Users $user */
$user = current($this->users);
}
}
I think User[] should work. Don't remember where I found that.