Set properties before calling the parent constructor - php

In some particular situations, we need to call the parent constructor parent::__construct(); after properties initialization like the bellow code (Symfony Command code, for more details see Symfony documentation):
<?php
class CreateUserCommand extends Command
{
// ...
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager, bool $requirePassword = false)
{
// best practices recommend to call the parent constructor first and
// then set your own properties. That wouldn't work in this case
// because configure() needs the properties set in this constructor
$this->entityManager = $entityManager;
$this->requirePassword = $requirePassword;
parent::__construct();
}
I wonder how we can deal with this case if we make the Constructor in PHP 8 property promotion style :
<?php
public function __construct(private EntityManagerInterface $entityManager, private bool
$requirePassword = false)
{
parent::__construct();
}

Related

Symfony 6 fetching objects without a controller

In previous versions of symfony, you could fetch objects like this
`
public function someMethod()
{
$method = $this->getDoctrine()->getRepository(Method::class)->findOneBy(array('id' => 1));
return $method;
}
`
This was easy because it meant that you could easily make global variables in the twig.yaml file and have dynamic content all around your page.
Now in symfony as far as i know, an argument of ManagerRegistry has to be passed as a argument all the time. Am I being too close minded or is there a work around for this problem?
I tried extending classes and have it pass down that way but it gave me the same workaround errors.
In a controller you can do either this :
class MyController extends AbstractController {
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
}
or
class MyController extends AbstractController {
{ ... }
public function someMethod(EntityManagerInterface $em) {
$em->getRepository(SomeClass::class)
}
}
I took a controller as an example but you can do this into your services. If you work with multiple entity manager, you can use the ManagerRegistry that extends AbstractManagerRegistry and use the method getManager($name)
There is no real workaround for this as you always need to inject it. Depending on what you want to achieve, may be there is solution that can help.
you can also directly inject the repository:
public function someMethod(EntityRepository $repository) {
$entities = $repository->findAll();
}
and of course you can inject it in the construct as well:
class MyController extends AbstractController {
private EntityRepository $repository;
public function __construct(EntityRepository $repository) {
$this->repository = $repository;
}
}
if you are on php 8 you can write:
class MyController extends AbstractController {
public function __construct(private EntityRepository $repository) {}
}

inheritance with diffrent constructors and obejct created in parent

The class A has a constructor with some Interfaces.
The class A has a __construct where a new object $this->foo = new FooController(); will be spawned
The class B extends A but has its own __construct with only one param. calling parent::__construct() with the same params of A::__construct does not work since this changes the B::__construct
class B accesses $this->foo which was created in the constructor of class A
How create $b = new B($some_variable='foo'); without reusing all constructor variables of A::class and have the $b->foo avaliable (which was created in A::class ?
Your hints are highly appreciated.
class A extends AbstractController
{
protected FooController $foo;
protected EntityManagerInterface $manager;
protected Security $security;
protected RequestStack $requestStack;
public function __construct(EntityManagerInterface $manager, Security $security, RequestStack $requestStack)
{
$this->manager = $manager;
$this->security = $security;
$this->requestStack = $requestStack;
$this->foo = new FooController();
}
}
class B extends A
{
public function __construct($some_variable)
{
parent::__construct()
$this->foo = $some_variable;
}
}

Call to a parent class member from child without calling constructor dependencies

I have my base controller and my content controller extending it like below, and I'm getting a Call to a member function error() on null so my question is:
Do I have to call the parent constructor?
If "yes" then is it better to have a service because the parent controller has some injected dependencies that I don't want to inject myself by calling the parent constructor
use Psr\Log\LoggerInterface;
class BaseController extends AbstractController
{
/** #var LoggerInterface */
protected $logger;
/**
* BaseController constructor.
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
class ContentController extends BaseController
{
private $contentRepository;
private $breadcrumbService;
public function __construct(
ContentRepository $contentRepository,
BreadcrumbInterface $breadcrumbService
) {
$this->contentRepository = $contentRepository;
$this->breadcrumbService = $breadcrumbService;
}
public function contentPage(...)
{
try {
....
} catch (\Throwable $exception) {
$this->logger->error(...);
throw $exception;
}
}
You do not need to call the constructor. But you do need to set the $logger property if you want to use it.
class ContentController extends BaseController
{
private $contentRepository;
private $breadcrumbService;
public function __construct(
LoggerInterface $logger,
ContentRepository $contentRepository,
BreadcrumbInterface $breadcrumbService
) {
$this->contentRepository = $contentRepository;
$this->breadcrumbService = $breadcrumbService;
$this->logger = $logger;
}
}
With the above you no longer need to call parent::__construct(). But it's simply good practice, painless and harmless:
Just do:
public function __construct(
LoggerInterface $logger,
ContentRepository $contentRepository,
BreadcrumbInterface $breadcrumbService
) {
$this->contentRepository = $contentRepository;
$this->breadcrumbService = $breadcrumbService;
parent::__construct($logger);
}
But if the only purpose of BaseController is to provide some logging methods, it's probably better to simply inject the logger service in ContentController and use it directly.

Deal with 'new' keyword in PHP unit test

In my case I have a class such as:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = new LogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Logger class is not testable because in my code 'new' keyword exists, in other hand Logger class and EntityManager class registered singleton in container and can't inject model as dependency.
How to change class for change to testable class?
LoggerModel is a Doctrine entity and use in Laravel framework.
I solve this problem with a sample solution: Factory Pattern.
I need assertion, so when get a new model from factory assert it as mock.
And how?
I create a class with a method that can be a singleton service:
class LogFactory
{
public function makeLogEntity()
{
return new LogEntity();
}
}
In another service, inject factory class:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
/**
* #var LogFactory
*/
protected $logFactory;
public function __construct(EntityManager $entityManager, LogFactory $logFactory)
{
$this->entityManager = $entityManager;
$this->logFactory = $logFactory
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = $this->logFactory->makeLogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Now I have a service that is mock able and call $mock->willReturn() function in test.

Why factory is null in test form?

I am trying to test my form extending TypeTestCase class
class ProjectTypeTest extends TypeTestCase
{
private $entityManager;
private $securityContext;
private $translator;
private $formFactory;
protected function setUp()
{
// mock any dependencies
$this->entityManager = $this->createMock("Doctrine\ORM\EntityManagerInterface");
$this->securityContext = $this->createMock("Symfony\Component\Security\Core\SecurityContextInterface");
$this->translator = $this->createMock("Symfony\Component\Translation\TranslatorInterface");
$this->formFactory = $this->createMock("AppBundle\FormTemplate\Factory\FormFactory");
}
public function testSubmitValidData()
{
$type = new ProjectType($this->entityManager,$this->securityContext, $this->translator, $this->formFactory);
$this->factory->create($type);
}
}
But, When I called $this->factory->create($type), returns :
Call to a member function create() on null
the factory property is null !
I'am using Symfony\Component\Form\Test\TypeTestCase and also I've used use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase and returns the same outcome.
Or, What am I doing bad ?.
Or, How do I test a Form ?
The problem is $this->factory is not being initialized. That should be done by FormIntegrationTestCase, which is extended by TypeTestCase and then by your test class.
Your setUp() is overriding the original setUp(), which initializes $this->factory, so you should call the parent one:
protected function setUp()
{
parent::setUp();
// mock any dependencies
$this->entityManager = $this->createMock("Doctrine\ORM\EntityManagerInterface");
$this->securityContext = $this->createMock("Symfony\Component\Security\Core\SecurityContextInterface");
$this->translator = $this->createMock("Symfony\Component\Translation\TranslatorInterface");
$this->formFactory = $this->createMock("AppBundle\FormTemplate\Factory\FormFactory");
}

Categories