I've a serious problem with a Symfony 2.0.16 installation. Cache file generated for a simple service is wrong.
One service replacer.factory, sort of factory one. I'll show only a simplified version:
/**
* #DI\Service("replacer.factory")
*/
class ReplacerFactory
{
/**
* #DI\InjectParams({"container" = #DI\Inject("service_container")})
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getReplacer($object)
{
$replacer = new NewsletterReplacer($this->container);
// Return the instance of NewsletterReplacer class
return $replacer->setInstance($object);
}
}
And this is the instance returned, again a bit simplified:
class NewsletterReplacer
{
private $container;
private $instance;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function setInstance(Newsletter $newsletter)
{
$this->instance = $newsletter;
}
}
For some reason, cache file generated is completely wrong.
In fact, this is part of appDevProjectContainer.php file, after the command php app/console cache:clear --env=dev --no-debug:
protected function getReplacer_FactoryService()
{
return $this->services['replacer.factory']
= new \Acme\HelloBundle\Service\Replacer\NewsletterReplacer();
}
It should be instead:
protected function getReplacer_FactoryService()
{
return $this->services['replacer.factory']
= new \Acme\HelloBundle\Service\Replacer\ReplacerFactory($this);
}
So, what's wrong and how can i solve it?
It was a PHP bug with annotation along with JMSDiExtraBundle, see this issue. Solved updating PHP 5.3.3-7.
Related
I use Symfony 4 with messenger and I use a worker who consumes my messages as a long-running process.
I have a bug with doctrine if I delete my post and recreate a new one and I dispatch my message. $post have the old data and not the new one.
I have tried a lot of things and nothing work, it works when I restart my worker.
class ChannelMessageHandler implements MessageHandlerInterface
{
private $channel;
private $bus;
public function __construct(ChannelService $channel, MessageBusInterface $commandBus)
{
$this->channel = $channel;
$this->bus = $commandBus;
}
public function __invoke(ChannelMessage $channelMessage)
{
$error = $this->channel->handleChannel($channelMessage->getUser(), $channelMessage->getService());
if ($error) {
throw new Exception($error[0]);
}
$this->bus->dispatch(new FeedMessage($channelMessage->getUser(), $channelMessage->getService()));
}
}
}
My MessageHandler call a service :
class ChannelService implements ContainerAwareInterface
{
use ContainerTrait;
protected $em;
protected $logger;
public function __construct(EntityManagerInterface $entityManager, LoggerInterface $logger)
{
$this->em = $entityManager;
$this->logger = $logger;
}
public function handleChannel($userId, $serviceName)
{
$user = $this->em->getRepository('App:User\Authentication\User')->findOneById($userId);
$post = $user->getPost();
return $this->getUserAnalyticBy($post, $serviceName);
}
thanks a lot
I decided, that it will be fine if I use data providers but when i try to generate code coverage whole tested class has 0% coverage.. Can someone tell me why?
Test class:
class AuthorDbManagerTest extends AbstractDbManagerTest
{
public function setUp()
{
parent::setUp();
}
/**
* #dataProvider instanceOfProvider
* #param bool $isInstanceOf
*/
public function testInstances(bool $isInstanceOf)
{
$this->assertTrue($isInstanceOf);
}
public function instanceOfProvider()
{
$manager = new AuthorDbManager($this->getEntityManagerMock());
return [
"create()" => [$manager->create() instanceof Author],
"save()" => [$manager->save(new Author()) instanceof AuthorDbManager],
"getRepository" => [$manager->getRepository() instanceof EntityRepository],
];
}
}
Tested class:
class AuthorDbManager implements ManagerInterface
{
protected $entityManager;
protected $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(Author::class);
}
public function create(array $data = [])
{
return new Author();
}
public function getRepository(): EntityRepository
{
return $this->repository;
}
public function save($object): ManagerInterface
{
$this->entityManager->persist($object);
$this->entityManager->flush();
return $this;
}
}
Why my code coverage is 0% on AuthorDbManager?
Screen
The data in the DataProvider is collected before the actual tests start - and there is nothing useful being tested within the testInstances() method.
If you passed the classname and expected class into testInstances($methodName, $expectedClass):
public function testInstances(callable $method, $expectedClass)
{
$this->assertInstanceOf($expectedClass, $method());
}
The dataprovider could return a callable, and the expected result:
"create()" => [[$manager,'create'], Author::class],
then you'd at least be running the code with in the actual test. You may also be better to just pass back a string methodname - 'create', and run that with a locally created $manager instance - $manager->$method() in the test.
In general, it's best to test something as specific as you can - not just letting it convert to a true/false condition.
We are using PHPUnit to test parts of our application. In some tests, we want to change the value of a parameter or override a service (but only for that test, not for all tests).
What is the recommended way to configure Symfony's container on the fly in tests?
The problem we have met is that the container doesn't recompile itself when config is set on the fly (because it only recompiles itself when files have changed).
This is how we proceed for now:
class TestKernel extends \AppKernel
{
public function __construct(\Closure $containerConfigurator, $environment = 'test', $debug = false)
{
$this->containerConfigurator = $containerConfigurator;
parent::__construct($environment, $debug);
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
parent::registerContainerConfiguration($loader);
$loader->load($this->containerConfigurator);
}
/**
* Override the parent method to force recompiling the container.
* For performance reasons the container is also not dumped to disk.
*/
protected function initializeContainer()
{
$this->container = $this->buildContainer();
$this->container->compile();
$this->container->set('kernel', $this);
}
}
Then we have added this method in our PHPUnit base test class:
/**
* Rebuilds the container with custom container configuration.
*
* #param \Closure $containerConfigurator Closure that takes the ContainerBuilder and configures it.
*
* Example:
*
* $this->rebuildContainer(function (ContainerBuilder $container) {
* $container->setParameter('foo', 'bar');
* });
*/
public function rebuildContainer(\Closure $containerConfigurator) : ContainerInterface
{
if ($this->kernel) {
$this->kernel->shutdown();
$this->kernel = null;
$this->container = null;
}
$this->kernel = new TestKernel($containerConfigurator);
$this->kernel->boot();
$this->container = $this->kernel->getContainer();
return $this->container;
}
As of Symfony 5, there is a possible shortcut. As long as all you need is to set parameters, you can simply change them in the $_ENV variable. I.e. the following works
services.yaml
services:
App\Controller\ApiController:
arguments:
$param: '%env(my_param)%'
ApiController.php
class ApiController extends AbstractController
{
public function __construct(string $param)
{
$this->param = $param;
}
...
}
test
class ApiControllerTest extends WebTestCase
{
public function testSomething(): void
{
$_ENV['param'] = 'my-value';
$client = self::createClient();
$client->request(...)
}
...
}
You do not test the container or config in Unit Tests.
In Unit Tests the goal is to test the units encapsulated without the full application stack.
For functional tests the recommended way is to edit it in the inherited config under app/config/config_test.yml
All values from config_dev.yml can be overridden there.
We've built up a set of data fixtures to seed the database with all our reference values. We are also using the DoctrineMigrationsBundle to manage schema updates. We would like to trigger the fixture load within our initial schema migration class so the system gets populated before running any additional schema updates.
I found in the docs that you can make migration classes container aware, but I can't figure out how to jump from that to calling/running the data fixtures. I haven't found any good answers on Stackoverflow or via google. Has anyone done this and can point me in the right direction? (or have suggestions on a better way to manage seed data in conjunction with schema migrations). Thanks.
This is using Symfony Version: 2.4
This is interesting question. I've found the "dirty" solution, but it works well.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Version20140811164659 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... your code here
}
public function postUp(Schema $schema)
{
// here you have to define fixtures dir
$this->loadFixtures('src/Acme/BlogBundle/DataFixtures/ORM');
}
public function down(Schema $schema)
{
// ... your code here
}
public function loadFixtures($dir, $append = true)
{
$kernel = $this->container->get('kernel');
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->setAutoExit(false);
//Loading Fixtures
$options = array('command' => 'doctrine:fixtures:load', "--fixtures" => $dir, "--append" => (boolean) $append);
$application->run(new \Symfony\Component\Console\Input\ArrayInput($options));
}
}
This solution simply running console command php app/console doctrine:fixtures:load --fixtures=src/Acme/BlogBundle/DataFixtures/ORM --append after "up" migration.
Sorry for poore English. If you'll find clear solution, share it ;)
I've made a migration class to address this very problem. The code is essentially inspired from the doctrine:fixtures:load command.
<?php
namespace AppBundle\Migrations;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AbstractFixturesAwareMigration extends AbstractMigration implements ContainerAwareInterface
{
private $container;
private $fixtures;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
protected function getContainer()
{
return $this->container;
}
protected function addFixture(FixtureInterface $fixture)
{
if(null === $this->fixtures) {
$this->fixtures = new ContainerAwareLoader($this->getContainer());
}
$this->fixtures->addFixture($fixture);
return $this;
}
protected function executeFixtures($em = null, $append = true, $purgeMode = ORMPurger::PURGE_MODE_DELETE)
{
$em = $this->getContainer()->get('doctrine')->getManager($em);
$purger = new ORMPurger($em);
$purger->setPurgeMode($purgeMode);
$executor = new ORMExecutor($em, $purger);
$executor->execute($this->fixtures->getFixtures(), $append);
$this->fixtures = null;
return $this;
}
}
Usage is pretty straightforward:
<?php
namespace Application\Migrations;
use AppBundle\Migrations\AbstractFixturesAwareMigration
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170726102103 extends AbstractFixturesAwareMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
// [...]
}
public function postUp(Schema $schema)
{
// LoadMyData can be any fixture class
$this->addFixture(new LoadMyData());
$this->executeFixtures();
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
// [...]
}
}
I have a project with a lot of tests class like
class MyTest extends BaseTestCase
{
public function __construct()
{
parent::__construct();
$this->em = $this->get('doctrine')->getManager();
}
public function setUp() {
$this->init();
//load sql data for the tests
$path = $this->get('kernel')->locateResource('#Bundle/Data/Test.sql');
$content_file_sql_data = file_get_contents($path);
$stmt = $this->em->getConnection()->prepare($content_file_sql_data);
$stmt->execute();
$stmt->closeCursor();
}
/*
* Then we do a lot of tests using the database
*/
}
They all extends my BaseTestCase:
abstract class BaseTestCase extends \PHPUnit_Framework_TestCase {
protected $_container;
protected $kernel;
public function __construct() {
parent::__construct();
$this->kernel = new \AppKernel("test", true);
$this->kernel->boot();
$this->_container = $this->kernel->getContainer();
$this->init();
}
//empty the database before each test class
public function init() {
$this->_application = new Application($this->kernel);
$this->_application->setAutoExit(false);
//rebuild and empty the database
$this->runConsole("doctrine:schema:drop", array("--force" => true));
$this->runConsole("doctrine:schema:create");
}
Since I have a lot of tests, i have recently got some errors PDOException: SQLSTATE[08004] [1040] Too many connections. It's like phpunit never close database connection, and around 100 tests I get this error for all the other tests.
How can i fix it?
I tried to put a last test doing $this->em->close() at the end of each test class but it didn't solve it
Some additional information: i'm pretty sure I don't have an issue with ONE test, because if I change the order of the test suite, the error appears around the same amount of tests class passed
My solution was to override shutdown method in my Bundle class:
public function shutdown()
{
if ('test' == $this->container->getParameter('kernel.environment')) {
/* #var EntityManager $em */
$em = $this->container->get('doctrine.orm.default_entity_manager');
$em->getConnection()->close();
}
}