I know that I can't extend two classes in php but I wonder, if I need to test a class with PHPUnit_Framework_TestCase but the class also interacts with a database using and so I need to use the PHPUnit_Extensions_Database_TestCase.
Can I use both in the same test class or I need to have two separate ones?
This is part of the class I am trying to test:
<?php
use Slim\Slim;
/**
* All ad rules to match against
*/
class AdRules {
public $site;
public $placement;
protected $db = null;
protected $filter = array();
function __construct(){
}
/*some more methods*/
function getRules() {
$DBH = $this->getDbh();
$where = $this->getWhereClause();
if (!empty($where)) {
$where = 'WHERE '.$where;
}
$query = "select * from rules {$where} order by site, placement, dof_count asc";
try {
$STH = $DBH->query($query);
$rules = $STH->fetchAll(PDO::FETCH_CLASS, 'AdRule');
}
catch(PDOException $e) {
Slim::getInstance()->log->error($e);
}
return $`enter code here`rules;
}
}
It already extends PHPUnit_Framework_TestCase.
abstract class PHPUnit_Extensions_Database_TestCase extends PHPUnit_Framework_TestCase see definition
Although, in fact all it add is a use PHPUnit_Extensions_Database_TestCase_Trait;
So you could just add that 'use' line and have your testclass extend the original TestCase as you would normally, and still have all the functionality of the Database_TestCase as well.
Related
I'm trying to do a unit test in the environment as the title says. However, the method contained in the class under test requires an entity instance as an argument. So I'm trying to get the above entity in a generic homebrew test class that extends the TestCase class but I can't figure out how to do it.
I have little experience with functional testing. I used fixtures at that time, so I'm guessing that I should use fixtures this time too. Is that correct? I would appreciate if you could teach me how to do that as well.
Please let me know, even a little information.
below is the test class, and the interface of the test target.
ps. we are using nelmio/alice bundle.
<?php
declare(strict_types=1);
namespace xxx\xxxx\Tests\Customize\Server\AlladinOffice;
use Codeception\PHPUnit\TestCase;
use Customize\Service\AlladinOffice\CodeFormatter;
use Customize\Service\AlladinOffice\CustomerCode;
use Doctrine\Common\Persistence\ObjectManager;
use Eccube\Repository\CustomerRepository;
use PHPUnit\Framework\MockObject\MockObject;
use Proxies\__CG__\Eccube\Entity\Customer;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CustomerCodeTest extends KernelTestCase
{
/**
* #var CustomerCode
*/
private $sut;
/**
* #var CustomerRepository|MockObject
*/
private $customerRepository;
protected function setUp()
{
$codeFormatter = new CodeFormatter();
$this->sut = new CustomerCode($codeFormatter);
$this->customerRepository = $this->createMock(CustomerRepository::class);
parent::setUp();
}
public function testGetCustomerCode()
{
$actual = $this->sut->getCustomerCode(null);
$expected = 1000000000;
$this->assertEquals($expected, $actual);
// $customer = $this->customerRepository->findBy([], ['id' => 'ASC']);
// dump($customer);exit();
// $actual = $this->sut->getCustomerCode($customer);
$customer = new Customer;
//$customer->set
//$this->customerRepository
//->expects($this->any())
//->method('find')
//->willReturn($);
}
}
<?php
declare(strict_types=1);
namespace Customize\Service\AlladinOffice;
use Eccube\Entity\Customer;
interface CustomerCodeInterface
{
public function getCustomerCode(?Customer $customer): string;
public function getChannelCode(?Customer $customer): string;
public function getRankCode(?Customer $customer): string;
}
for example a detailed method to test.
public function getCustomerCode(?Customer $customer): string
{
if (!$customer) {
return self::DEFAULT_CUSTOMER_CODE;
}
$customerRank = $customer->getXxxCustomer()->getCustomerRank();
$customerChannel = $customerRank ? $customerRank->getCustomerChannel() : null;
if (!$customerChannel || !$customerChannel->isExportToAO()) {
return self::DEFAULT_CUSTOMER_CODE;
}
return $this->formatter->getCustomerCodeFromCustomerId($customer->getId());
}
I have written a class BasicRepository in order to use it instead of the EntityRepository to add some basic modification like remove all deleted-flaged items.
<?php
namespace AppBundle\Repository;
use AppBundle\DataFixtures\ORM\LoadEventPrioData;
use AppBundle\Entity\Location;
use Doctrine\ORM\EntityRepository;
class BasicRepository extends EntityRepository
{
public function createQueryBuilder($alias, $indexBy = null)
{
$query = parent::createQueryBuilder($alias);
dump(parent::getClassName());
dump($this->getClassName());
if (property_exists($this->getClassName(), 'isDeleted')) {
dump("Ping");
$query->andWhere($alias.'.isDeleted = :false')->setParameter('false', false);
}
else {
dump("Pong");
}
return $query;
}
}
Controller:
...
public function searchAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$meta = new ClassMetadata('AppBundle:Location');
$er = new BasicRepository($em, $meta);
$query = $er->createQueryBuilder('u');
...
My aim is that - if the property "isDeleted" (boolean) exists in the Entity - the Query should contain an additional Where-Statement.
For some strange reason property_exists always return false - even when the property exits in the class.
I get your idea. The correct place you're looking for is Doctrine Filters. Check this package: https://github.com/DeprecatedPackages/DoctrineFilters#usage
There you can find example exactly with your use case:
<?php
use Doctrine\ORM\Mapping\ClassMetadata;
use Symplify\DoctrineFilters\Contract\Filter\FilterInterface;
final class SoftdeletableFilter implements FilterInterface
{
/**
* {#inheritdoc}
*/
public function addFilterConstraint(ClassMetadata $entity, $alias)
{
if ($entity->getReflectionClass()->hasProperty('isDeleted')) {
return "$alias.isDeleted = 0";
}
return '';
}
}
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 some entity, but i want to validate a maximum of X entries in db.
For example, no more than 5 categories in DB are allowed.
I try to see if some constrain validation solve my trouble, but i dont find nothing useful.
Thank in advance
If you need to validate your entity in multiple places or do this type of validation for more than one entity class you could write a custom validation constraint to do this. It does feel a bit like overkill but it is technically the 'correct' solution. This is documented in the Symfony Manual in How to create a Custom Validation Constraint. For example:
Constraint class:
<?php
namespace Your\Bundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class MaxEntries extends Constraint
{
public $message = 'The maximum %max% %name% entries already exist.';
public $max = 5;
public function validatedBy()
{
return 'maxEntries';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
ConstraintValidator class:
<?php
namespace Your\Bundle\Validator\Constraints;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class MaxEntriesValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($protocol, Constraint $constraint)
{
/** #var MaxEntries $constraint */
$protocolClass = new \ReflectionClass(get_class($protocol));
$entityName = $protocolClass->getShortName();
// alternative to next 2 lines - you could use dql to do a select count
$repository = $this->entityManager->getRepository('YourBundle:' . $entityName);
$entities = $repository->findAll();
if (count($entities) == $constraint->max)
{
$this->context->addViolation($constraint->message, array(
'%max%' => $constraint->max,
'%name%' => $entityName,
));
}
}
}
services.yml:
validator.max_entries:
class: Bullitt\TargetNeutral\SpectatorBundle\Validator\Constraints\MaxEntriesValidator
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: validator.constraint_validator, alias: maxEntries }
validation.yml:
Your\Bundle\YourEntity:
constraints:
- Your\Bundle\Validator\Constraints\MaxEntries:
# You can override the default constraint attributes here
message: "Failed! The maximum %max% %name% entries already exist."
max: 42
Note, you might be able to come up with a better name than MaxEntries (I normally spend more time thinking of the most meaningful name). Also, the validator class currently contains no protection for applying it to a class that is not a doctrine entity.
You will probably have to implement this yourself. I haven't seen any such thing, but it wouldn't be difficult to do one your own. Just add a function to check how many entries you have in the db, and call this function before you insert anything.
Something like this can be done:
public function newAction(){
$isAllowed = $this->checkMaxRecords();
if($isAllowed){
//save to the database
}
}
private function checkMaxRecords(){
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'SELECT c
FROM AcmeStoreBundle:Category c'
);
$category = $query->getResult();
if(count($category) >= 5){
return false;
}
return true;
}
This is untested code, but something along these lines should get you on the right track.
I have entities:
abstract class AbstractEntity
{
private $someField;
}
/**
* ...
* #ORM\Entity(repositoryClass="ConcreteEntityRepository")
*/
class ConcreteEntity extends AbstractEntity
{
private $otherField;
}
class ConcreteEntityRepository extends EntityRepository
{
public function getSomething()
{
$qb = $this->getEntityManager()->createQueryBuilder()
->select('t')
->from('MyBundle:ConcreteEntity', 't');
$result = $query->getResult();
}
}
Result will be with correct count of fields but values of parent class will be null.
How can I correctly get all the fields?
And when I try to use:
->select('t.someField') // Error
->select('t.otherField') // Good
My guess is you can't use private properties in your abstract class. Try using protected ones.
The documentation does the same: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html.