Error: Call to a member function add() on null - symfony

I'm trying to use the embed multiple form in a single form. I have an issue in setting a value for the sub entity. I have included required namespace and methods for this concept. Below is the line of code in controller
$ticket = new EventTicket();
$sale1 = new EventSaleItem();
$sale1->setName('value1');
$ticket->getSales()->add($sale1);
// Ticket entity
public function getSales()
{
return $this->sales;
}

You have to initialize the sales in your Ticket constructor to avoid this error:
// Ticket entity
use Doctrine\Common\Collections\ArrayCollection;
Class Ticket{
public function __construct()
{
$this->sales = new ArrayCollection();
//...
}

Related

How do I pass an id variable to a class which has dependency injection Symfony 3.4

I have class that uses Dependency Injection, with two other classes, this works fine. But I want to then instantiate the Merchant class in a controller and pass an id . Thing I don't get is the constructor is expecting more values 'CurrencyConverter' and 'TransactionTable' so how can I complete the code ? ?, which I don't need to pass. So I'm not clear how to make it work, thanks
Model Class
namespace TransactionBundle\Model;
class Merchant
{
public $_transactions;
public $_currencyConverter;
public $_id;
public function __construct($id,CurrencyConverter
$currencyConverter,TransactionTable $transactions)
{
$this->_transactions = $transactions;
$this->_currencyConverter = $currencyConverter;
$this->_id = $id;
}
public function getTransactions() {
$this->_currencyConverter->convert();
$this->_transactions->getData();
}
}
trying to instantiate in the controller
$merchant = new Merchant(2,?,?);
$results = $merchant->getTransactions();
If the class has a dependency on something that is not in the container, then the class cannot be loaded from the container.
Either pass the dependencies yourself in the controller:
$merchant = new Merchant(2, $currencyConverter, $transactions);
Or use a factory service in the container:
class MerchantFactory {
private $currencyConverter;
private $transactions;
// constructor omitted for brevity
public function getMerchantForId($id) {
return new Merchant($id, $this->currencyConverter, $this->transactions);
}
}
Then in your controller, depend on the factory:
$merchant = $this->merchantFactory->getMerchantForId(2);

Zend Framework 3 Redirect from factory

My Controller has a Factory that gives it a Form
$formManager = $container->get('FormElementManager');
return new MyController(
$formManager->get(MyForm::class)
);
My Form has also a Factory that gives it an AuthenticationService
return new MyForm(
$container->get(AuthenticationService::class)
);
That way I can check in the form if the user has identity.
But how can i redirect him from the form?
Just like in a Controller?
if(!$authService->hasIdentity()) {
return $this->redirect()->toRoute('myRoute);
}
Or how can i redirect from a (Controller and/or Form) Factory?
A possible solution for your issue could be the possibilty of using the build method with the factory call.
You haven 't shown your factories, so I will use some standard examples, which explain the solution.
The first approach is not injecting the whole form to the controller. Instead just inject the form element manager. So you can use the build method of the factory inside your controller.
The controller factory
namespace Application\Controller\Factory;
use Application\Controller\YourController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class YourControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$formElementManager = $container->get('FormElementManager');
return new YourController($formElementManager);
}
}
This differs from your original factory. Only the form element manager is injected to the controller. This holds a few advantages for you. One of this is the build method of the manager.
The Controller
namespace Application\Controller;
class YourController extends AbstractActionController
{
protected $formElementManager;
public function __construct($formElementManager)
{
$this->formElementManager = $formElementManager;
}
public function indexAction()
{
$user = $this->currentUser();
if ($user === null) {
$this->redirect('to/somewhere/the/user/belongs');
}
// here 's the magic!
$form = $this->formElementManager->build(YourForm::class, [
'userID' => $user->getUserId(),
]);
// some form stuff follows here
}
}
As the form was not injected directly to your controller but the form element manager, you can use the form element manager instead inside the controller. This offers you the opportunity to use the build function. With this function you can add some options to your form factory. In this case I 'm using the user id for the form factory.
If there 's no valid user, no form will be created because an exception is thrown before.
The Form Factory
The form factory creates a new instance of your form. All needed dependencies should be created in the factory. How the build function works here, I 'll explain later in the answer.
namespace Application\Form\Factory;
use Application\Form\YourForm;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class YourFormFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$selectOptions = [];
if ($options !== null) {
if (isset($options['userID])) {
$tablegateway = $container->get(YourTableGateway::class);
$selectOptions = $tablegateway->findOptionsByUserId($options['userID]);
}
}
$form = $container->get(YourForm::class);
if (count($selectOptions))
$form->get('YourSelectElement')->setValueOptions($selectOptions);
return $form;
}
}
This factory does all you need. Via the build method you hand over the user id. If a user id is present a table gateway is created from wich you retrieve select options by the given user id. These options will be set to the form field. This logic is kept in the factory to keep the form class itself clean and simple.
With this solution you don 't need the auth service in your form. Your form is only generated when a valid user id is given. Your form instance will not crash, if there 's no user id given. The only conceivable case could be a form with default or no select options for the specific field.
Hope this helps a bit.

Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests

Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.

Undefined variable in PHPUnit Test Class

I have a test class that reports an undefined variable and I cannot seem to understand what the issue is.
Basically the listener below is suppose to listen to an application boot event documented in the class below:
<?php
namespace Colleen\Core\Event\Application;
final class ApplicationBootedEvents
{
const APP_BOOTED = 'application.booted';
}
My event class is as shown below which receives an instance of the application itself.
<?php
namespace Colleen\Core\Event\Application;
use Symfony\Component\EventDispatcher\Event;
use Colleen\Core\Application;
/**
* The application.booted event is dispatched each time
* an application instance is created in the system.
*
*/
class ApplicationBootedEvent extends Event
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function getApplication()
{
return $app;
}
}
These two classes to me look perfect according to Symfony's documentation on the Event Dispatcher Component. Following is the listener class that is suppose to listen to ApplicationBootedEvents::APP_BOOTED event.
<?php
namespace Colleen\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
class ApplicationBootedListener
{
public function onBoot(ApplicationBootedEvent $event)
{
$container = $event->getApplication()->getContainer();
$container->set('class.dispatcher', '\\Symfony\\Component\\EventDispatcher\\EventDispatcher');
}
}
The Listener class does nothing at the moment and my test case is to test whether the "class.dispatcher" key exist on my container which simple extends Pimple and is made available through the Application Object.
Below is my test that shows how these will eventually be used in my front controller or any class that stands between them and the front controller.
<?php
namespace Colleen\Qa\Core\Event\Application\Listener;
use Colleen\Core\Event\Application\Listener\ApplicationBootedListener;
use Colleen\Core\Event\Application\ApplicationBootedEvents;
use Colleen\Core\Event\Application\ApplicationBootedEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Colleen\Core\Container\Container;
use Colleen\Core\Application;
class AppliocationBootedListenerTest extends \PHPUnit_Framework_TestCase
{
public function testApplicationBootListener()
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(
ApplicationBootedEvents::APP_BOOTED, array(
new ApplicationBootedListener(), 'onBoot'
));
$app = $dispatcher->dispatch(ApplicationBootedEvents::APP_BOOTED, new ApplicationBootedEvent(new Application(new Container())))->getApplication();
$expected = '\\Symfony\\Component\\EventDispatcher\\EventDispatcher';
$actual = $app->getContainer()->get('class.dispatcher');
$this->assertSame($expected, $actual);
}
}
The idea is to test whether the Listener gets called and if it is able to feed our application object's container with all the necesary objects we will need to get our web framework to work.
Below is the output I get as a result if running this test case.
There's an error in your ApplicationBootedEvent.php file, on line 24 as the stack trace suggested..
Change
public function getApplication()
{
return $app;
}
To
public function getApplication()
{
return $this->app;
}

Add custom property to all symfony2 controllers

is there any way to preprocess controller data somehow. I'm going to take param from session, validate it and assign it as controller property and use it as $this->myVar inside actions of some controller or all of them if possible. Using controller's constructor gives me nothing, I couldn't access request and session data. Thanks!
UPD:
Thanks, jkucharovic, very good solution.
Also there is a bit dirtier solution, without injecting: setContainer() method, which has been called straight after $controller = new Controller();
use Symfony\Component\DependencyInjection\ContainerAwareInterface,
Symfony\Component\DependencyInjection\ContainerInterface;
class AppServiceController extends Controller {
private $my_property;
/**
* Used as constructor
*/
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->my_property = 'foo';
// your controller code
}
}
I'm not sure what you wan't to do is very usefull. A Controller instance will be created each time the controller is called. The session and request will be different each time you call the controller.
I think you should create a BaseController class extending Controller class with a shortcut method to access your MyVar value in session.
class BaseController extends Controller
{
public function getMyVar()
{
return $this->get('session')->get('MyVarSessionKey');
}
}
All your other Controller will extend from this BaseController.
To get the request, just use the shortcut method provided by Controller class, Controller::getRequest().
If you want to use services in __construct method, you have to inject that services first. Then you can use them before any other methods. For example:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
public function __construct(Request $request, Session $session)
{
…
}

Resources