Use arguments with JMS #VirtualPropery - doctrine-orm

/**
* #Serializer\VirtualProperty
* #Serializer\SerializedName("datasByStatus")
*/
public function getNbOfDatasByStatus($status) {
$datas = $this->getDatas();
$nbOfDatas = 0;
for($i=0; $i<sizeof($datas); $i++){
if($datas[$i]->isValide() === $status){
$nbOfDatas ++;
}
}
return $nbOfDatas;
}
This keeps failing returning :
"Warning: Missing argument 1 for Bundle\\ModelBundle\\Entity\\MyEntity::getNbOfDatasByStatus(), called in api/vendor/jms/serializer/src/JMS/Serializer/Metadata/PropertyMetadata.php on line 89 and defined
I'm sure there is no missing parameter at the different places where this method is called. I've even tried to remove them all and still the error is raised.
If I add a default value to the argument, it works but the actually passed argument is ignored.
I've not found anything searching the docs... Can #VirtualProperty actually handle arguments?

I had the same problem and i've not found a documented solution. I think that a workaround is create a generical method to receive the parameter filter and additionally create methods calling it according to business needed with the #VirtualProperty.
Example:
function myGenericFilter($parameter)
{
<do something>
:
return result;
}
/**
* #VirtualProperty
**/
function getManagers()
{
return $this->myFilter(1);
}
/**
* #VirtualProperty
**/
function getStaff()
{
return $this->myFilter(2);
}

Related

PHPUnit test method won't receive array as a paremeter

I have a a PHPUnit test where the third method won't receive the array returned in the second method. Take a look:
<?php
namespace Tests\Unit;
use Tests\TestCase;
class TesteTest extends TestCase
{
public function testFirst()
{
$test = ['id' => 123];
$this->assertNotEmpty($test);
return $test;
}
/** #depends testFirst */
public function testSecond(array $test)
{
$this->assertNotEmpty($test);
}
/** #depends testSecond */
public function testThird(array $test)
{
$this->assertNotEmpty($test);
}
}
This the response I have from PHPUnit:
Argument 1 passed to Tests\Unit\TesteTest::testThird() must be of the type array, null given, called in C:\Users\edgar\Documents\Projetos\Solarium\solarium-api\vendor\phpunit\phpunit\src\Framework\TestCase.php on line 1527
Any ideas of where I might be going wrong?
Idea is simple. Check documentation.
A producer is a test method that yields its unit under test as return value.
A consumer is a test method that depends on one or more producers and their return values.
Your producer testSecond return nothing to your consumer testThird
public function testSecond(array $test)
{
$this->assertNotEmpty($test);
return $test;
}

PHPUnit 9 - Mocking void methods

I just upgraded the phpunit 7.5.20 to phpunit 9.5.0 and I'm facing a lot of errors (good ones actually), but not 100% sure how to workaround with some of those errors.
Just looking for some ideas to fix the following error:
Method setDummyStuff may not return value of type NULL, its return declaration is "void"
It happens only when you're creating a createConfiguredMock() and passing a null method as a argument.
Here's my test:
<?php
use Lib\IDummyCode;
class DummyTest extends PHPUnit\Framework\TestCase
{
public function setUp(): void
{
parent::setUp();
}
public function testDummyThatReturnsVoid()
{
$this->createConfiguredMock(IDummyCode::class, [
'setDummyStuff' => null
]);
}
}
And here's the dummy class:
<?php
namespace Lib;
interface IDummyCode
{
public function setDummyStuff(
int $testInt,
string $testString
): void;
}
Do you guys, have some thoughts about how to improve this?
Thanks a lot!
The second parameter to createConfiguredMock takes an associative array where the key is the method to mock and the value is the value the method should return. Since the setDummyStuff method can not return anything (void return type) it does not make sense to define a return value. It isn't the null value in particular. It will fail with any value.
So you could just leave that method out:
$mock = $this->createConfiguredMock(IDummyCode::class, []);
Which can also be written in a nicer way:
$mock = $this->createStub(IDummyCode::class);
If you need to verify that setDummyStuff was called, you have to set up an expectation.
$mock = $this->createMock(IDummyCode::class);
$mock->expects(self::once())
->method('setDummyStuff')
->with(123, 'Hello');

Compiler pass - resolve target entity doesn't load

I'm working on a bundle and i need to load a doctrine resolve_target_entities from a configuration parameter.
This article should be my solution, the fact is that using the bundle it seems not to load the "compiler pass class".
This is my bundle class
class PersonalBundle extends Bundle
{
public function build(ContainerBuilder $container){
parent::build($container);
$container->addCompilerPass(new ResolveTargetEntitiesPass());
}
}
This is the ResolveTargetEntitiesPass class
class ResolveTargetEntitiesPass implements CompilerPassInterface
{
/**
* {#inheritdoc}
*/
public function process(ContainerBuilder $container)
{
// Gets the custom entity defined by the user (or the default one)
$customEntityClass = $container->getParameter('personal.custom_class');
// Skip the resolve_target_entities part if user has not defined a different entity
if (DefaultClassInterface::DEFAULT_ENTITY_CLASS == $customEntityClass) {
return;
}
// Throws exception if the class isn't found
if (!class_exists($customEntityClass)) {
throw new ClassNotFoundException(sprintf("Can't find class %s ", $customEntityClass));
}
// Get the doctrine ResolveTargetEntityListener
$def = $container->findDefinition('doctrine.orm.listeners.resolve_target_entity');
// Adds the resolve_target_enitity parameter
$def->addMethodCall('addResolveTargetEntity', array(
DefaultClassInterface::DEFAULT_ENTITY_CLASS, $customEntityClass, array()
));
// This was added due this problem
// https://stackoverflow.com/a/46656413/7070573
if (version_compare(Version::VERSION, '2.5.0-DEV') < 0 && !$def->hasTag('doctrine.event_listener')) {
$def->addTag('doctrine.event_listener', array('event' => 'loadClassMetadata'));
} elseif (!$def->hasTag('doctrine.event_subscriber')) {
$def->addTag('doctrine.event_subscriber');
}
}
}
When i use the class it raises this error
Expected value of type "PersonalBundle\Entity\DefaultClass"
for association field "PersonalBundle\Entity\Group#$defaultClass", got
"App\Entity\CustomClass" instead.
As i said it seems not to load the ResolveTargetEntitiesPass...
Thanks
So i solved the problem changing the priority of the compiler pass.
I've tried to move the bundle on top in config/bundle.php and it started working, then following this https://symfony.com/blog/new-in-symfony-3-2-compiler-passes-improvements i've left the default type but increased the priority (from 0, default, to 1).
I'm not sure which service has been "downgraded" so if anyone has an idea it's welcome.
<?php
// ...
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
class PersonalBundle extends Bundle
{
public function build(ContainerBuilder $container){
parent::build($container);
$container->addCompilerPass(new ResolveTargetEntitiesPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
}
}

How to override the default ON_CALL action for just one EXPECT_CALL and go back to the default action later

I would like to test the method of my system, whose return value partially depends on the return value of the call to some kind of connection interface. In most cases I would like the IConnection to return true upon any kind of call to it's open(_, _) method. Except in one case, when I explicitly test for the condition with failed connection.
Example:
/*
* Some kind of network interface with method `open`
*/
class IConnection {
public:
IConnection() = default;
virtual ~IConnection() = default;
virtual bool open(const std::string& address, int port) = 0;
};
class ConnectionMock: public IConnection {
public:
MOCK_METHOD2(open, bool(const std::string& address, int port));
};
class MySystem {
public:
MySystem() = delete;
MySystem(std::shared_ptr<IConnection> connection): connection_(connection) {}
bool doSth() {
/*
* Do some things, but fail if connection fails
*/
bool connectionStatus = connection_->open("127.0.0.1", 6969);
if (!connectionStatus) {
return false;
}
// do other things
return true;
}
private:
std::shared_ptr<IConnection> connection_;
};
TEST(MySystemShould, returnFalseIfFailedToOpenConnectionAndTrueIfSucceeded) {
auto connectionMock = std::make_shared<NiceMock<ConnectionMock> >();
ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
MySystem system(connectionMock);
// if I don't specify Times test fill fail, because WillOnce automatically sets Times(1)
EXPECT_CALL(*connectionMock, open(_, _)).Times(AnyNumber()).WillOnce(Return(false));
/*
* Commented code below is not a good solution - after expectation retires
* the test will fail upon subsequent calls
*/
//EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).RetiresOnSaturation();
ASSERT_FALSE(system.doSth());
/*
* Code bellow allows me to avoid the warning
*/
//EXPECT_CALL(*connectionMock, open(_, _)).WillRepeatedly(Return(true));
ASSERT_TRUE(system.doSth());
}
The problems with my current solution is that when the EXPECT_CALL override becomes saturated, even though gmock goes back to the default action specified on ON_CALL, every subsequent call to open(_, _) is causing the following warning:
GMOCK WARNING:
/whatever.cpp:105: Actions ran out in EXPECT_CALL(*connectionMock, open(_, _))...
Called 2 times, but only 1 WillOnce() is specified - taking default action specified at:
/whatever.cpp:103:
even though I'm using NiceMock. I can get rid of the warning by specifying EXPECT_CALL with WillRepeatedly(Return(true)), but this is the duplication of my code in ON_CALL.
I would like to know, how can I override the default action specified with ON_CALL for just one call to IConnection::open, and then go back to the defaults, without causing gmock to print a warning. The perfect solution would be something similar to:
EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).DisableExpectationAfterSaturation();
but it doesn't exist. RetiresOnSaturation doesn't work as I would like, because it fails the test after getting saturated (doesn't match action specified with ON_CALL).
EDIT 2
The DoDefault() - feature comes close to what is asked in the question. It specifies that an action in EXPECT_CALL should go back to the default action specified by ON_CALL:
using ::testing::DoDefault;
// Default action
ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
// returns true once and then goes back to the default action
EXPECT_CALL(*connectionMock, open(_, _)
.WillOnce(Return(false))
.WillRepeatedly(DoDefault());
Initial answer
If the return value of IConnection::open depends on the parameters you can specify ON_CALL twice but with different arguments (or rather arguments instead of the placeholder):
ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
ON_CALL(*connectionMock, open("BAD_ADDRESS", 20)).WillByDefault(Return(false));
So any time the mocked method open will be called with arguments "BAD_ADDRESS" and 20, it will return false, and true otherwise.
Here is a simple example:
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;
class A {
public:
virtual bool bla(int a) = 0;
};
class MOCKA : public A {
public:
MOCK_METHOD1(bla, bool(int));
};
TEST(ABC, aBABA) {
MOCKA a;
ON_CALL(a, bla(_)).WillByDefault(Return(false));
ON_CALL(a, bla(1)).WillByDefault(Return(true));
EXPECT_CALL(a, bla(_)).Times(AnyNumber());
EXPECT_TRUE(a.bla(1));
EXPECT_TRUE(a.bla(1));
EXPECT_TRUE(a.bla(1));
EXPECT_FALSE(a.bla(2));
EXPECT_FALSE(a.bla(3));
EXPECT_FALSE(a.bla(4));
}
EDIT 1
I think now I understood the problem and if I did then the solution is very simple:
EXPECT_CALL(*connectionMock, open(_, _))
.Times(AnyNumber())
.WillOnce(Return(true))
.WillRepeatedly(Return(false));
When ConnectionMock::open will be called inside of MySystem::doSth it will once return true and then always return false no matter what the arguments are. In this case you also don't need to specify ON_CALL. Or do you definitely need to specify the actions with ON_CALL instead of EXPECT_CALL?

Embed a Collection of Forms Error: Could not determine access type for property

I am trying to embed collection of Tag forms to Service form, according to this tutorial. Tag and Service entities have many-to-many relationship.
Form is rendering correctly. But when I submit form, I get
Could not determine access type for property "tagList"
error. I don't understand why new Tag object is not added to the Service class by calling the addTag() method.
ServiceType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'label' => 'Title'
))
;
$builder->add('tagList', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)));
}
Service class
{
....
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="serviceList",cascade={"persist"})
*/
private $tagList;
/**
* #return ArrayCollection
*/
public function getTagList()
{
return $this->tagList;
}
/**
* #param Tag $tag
* #return Service
*/
public function addTag(Tag $tag)
{
if ($this->tagList->contains($tag) == false) {
$this->tagList->add($tag);
$tag->addService($this);
}
}
/**
* #param Tag $tag
* #return Service
*/
public function removeTag(Tag $tag)
{
if ($this->tagList->contains($tag)) {
$this->tagList->removeElement($tag);
$tag->removeService($this);
}
return $this;
}
}
Tag class
{
/**
* #ORM\ManyToMany(targetEntity="Service", inversedBy="tagList")
* #ORM\JoinTable(name="tags_services")
*/
private $serviceList;
/**
* #param Service $service
* #return Tag
*/
public function addService(Service $service)
{
if ($this->serviceList->contains($service) == false) {
$this->serviceList->add($service);
$service->addTag($this);
}
return $this;
}
/**
* #param Service $service
* #return Tag
*/
public function removeService(Service $service)
{
if ($this->serviceList->contains($service)) {
$this->serviceList->removeElement($service);
$service->removeTag($this);
}
return $this;
}
}
ServiceController
public function newAction(Request $request)
{
$service = new Service();
$form = $this->createForm('AppBundle\Form\ServiceType', $service);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($service);
$em->flush();
return $this->redirectToRoute('service_show', array('id' => $service->getId()));
}
return $this->render('AppBundle:Service:new.html.twig', array(
'service' => $service,
'form' => $form->createView(),
));
}
Could you please try to implement code from this URL?
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#owning-and-inverse-side-on-a-manytomany-association
First, please try to change mapped/inverse sides, and remove $service->addTag($this); from Tag::addService method.
Short version:
I just ran into this problem and solved it by adding a setter for the affected property:
Could not determine access type for property "tagList"
public function setTagList(Array $tagList)
{
$this->tagList = $tagList;
}
Long version:
The error message is signaling that Symfony is trying to modify the object's state, but cannot figure out how to actually make the change due to the way its class is set up.
Taking a look at Symfony's internals, we can see that Symfony gives you 5 chances to give it access and picks the best one in this order from top to bottom:
A setter method named setProperty() with one argument:
This is the first thing Symfony checks for and is the most explicit way to achieve this. As far as I'm aware this is the best practice:
class Entity {
protected $tagList;
//...
public function getTagList()
{
return $this->tagList;
}
//...
}
A combined getter and setter in one method with one argument:
It's important to realize that this method will also be accessed by Symfony in order to get the object's state. Since those method calls don't include an argument, the argument in this method must be optional.
class Entity {
protected $tagList;
//...
public function tagList($tags = null)
{
if($reps){
$this->tagList = $tags;
} else {
return $this->tagList;
}
}
//...
}
The affected property being declared as public:
class Entity {
public $tagList;
//... other properties here
}
A __set magic method:
This will affect all properties rather than just the one you intended.
class Entity {
public $tagList;
//...
public function __set($name, $value){
$this->$name = $value;
}
//...
}
A __call magic method (in some cases):
I wasn't able to confirm this, but the internal code suggests this is possible when magic is enabled on PropertyAccessor's construction.
Only using one of the above strategies is required.
Maybe the problem is that Symfony can't access that property?
If you look at where that exception is thrown (writeProperty method in the PropertyAccessor class) it says it can be thrown:
If the property does not exist or is not public.
In the tutorial you mentioned it has property $tags, and method addTag. I'm just guessing here, but maybe there's a convention where it tries to call a method names add($singularForm) and this is failing for you because the property is tagList and the method is addTag.
I'm not 100% sure, but you could try debugging by setting a stop point in that Symfony method to see why it's being thrown.
Maybe you forgot in the __construct() of Service class and Tag class to initialize $tagList and $serviceList like this ?
$this->tagList = new ArrayCollection();
$this->serviceList = new ArrayCollection();
This seems like an error with your constructor. Try this :
public function __construct()
{
$this-> tagList = new \Doctrine\Common\Collections\ArrayCollection();
}
It's a long shot, but looking at your annotations I think the problem might be related to your manyToMany relationship. Try to change the owning side and inverse side (Swap the relationship) unless you specifically need to update from both ends (In that case I think the only solution is to add the objects manually or use oneToMany relationships).
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view)
This is a problem related to Doctrine I have suffered before, see:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html
Based on Symfony 3.3.10
I actually faced this problem many and many times, finally once i discovered where this problem was coming from, depending on the name you give to your entity property it can happen that the adder and the remover for your collection property aren't exactly what you are expecting.
Example: Your entity properity name is "foo" and you would expect the adder to be called "addFoo" and remover "removeFoo", but then all of a sudden the "Could not determine access type for property" appear.
So you start going into fear searching for w/e problems in your code, instead you just have to look this file inside Symfony core files:
vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Inside this file there's a method called findAdderAndRemover.
Go there with your debugger and you will eventually find out that symfony searches for weird name for your adder/remover, they may actually end with "um" or "on" or "us" depending on the language (human language) you used to name them. Since i'm Italian this happen quite often.
Watch out for that, since the fix may be as simple as changing the name used for your add/remove method inside your entity to make them match with what Symfony core is looking for.
This happens to me when i use bin/console doctrine:generate:entities to create the methods automatically for me
If you are using symfony, and use EntityRepository instead of CollectionType, make sure you use the 'multiple' => true, on your form build, otherwise the input will be for one entity and not for many, therefore it will call the setTagList instead of using the methods addTagList and removeTagList.