I working with zf2.1.3 and doctrine 2. I was trying to hydrate information on my class and realize that the DoctrineModule\Stdlib\Hydrator\DoctrineObject doesn't work with fields that have underline on it, like cat_id.
Here an example:
/* namespace Application\Entity; */
class Foo
{
private $cat_id;
private $cat_name;
public function getCatId()
{
return $this->cat_id;
}
public function setCatName($name)
{
$this->cat_name = $name;
return $this;
}
public function getCatName()
{
return $this->cat_nome;
}
}
class Bar
{
private $id;
private $name;
public function getId()
{
return $this->id;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->nome;
}
}
/* namespace Application\Controller; */
use \DoctrineModule\Stdlib\Hydrator\DoctrineObject;
public function indexAction()
{
$hydrator = new DoctrineObject($this->getEntityManager(), 'Application\Entity\Foo');
$foo = $hydrator->hydrate(array('cat_name' => 'Frank Moraes'), new Foo());
\Zend\Debug\Debug::dump($foo, 'Foo Hydrator');
$hydrator = new DoctrineObject($this->getEntityManager(), 'Application\Entity\Bar');
$bar = $hydrator->hydrate(array('name' => 'Frank Moraes'), new Bar());
\Zend\Debug\Debug::dump($inscrit, 'Bar Hydrator');
}
This code returns the following:
Foo Hydrator
object(Application\Entity\Foo)
private 'cat_id' => null
private 'cat_name' => null
Bar Hydrator
object(Application\Entity\Foo)
private 'id' => null
private 'name' => 'Frank Moraes'
So my question is: Why Doctrine Hydrator doesn't work with fields that have underline in it?
How can I make this work?
Thank you!
Edited
Sorry for the long time with no answer. A have no access to SO on my work!
I tried the following:
$hydrator = new DoctrineObject($this->getEntityManager(), 'Application\Entity\Foo', false);
For the example I posted here, this false parameter works fine.
But, it didn't work when I'm binding the class on a form!
Someone have a clue?
Several months after this has been asked, but I've been checking the source code of the DoctrineObject hydrator just now, and I think this is what's going on:
By default, unless you construct the DoctrineObject hydrator with the byValue flag as false, the hydrator will work in byValue mode. What that means is that it tries to construct getter and setter method names from the values that you're trying to hydrate, and the way it does that is by calling ucfirst on the field name and prepending get/set to that.
So, for instance, you have cat_name, so it will try the getter method getCat_name which clearly is incorrect.
You have 4 choices, then:
A: camelCase your variable names
B: Set byValue to false (so that it tries to access the variables directly) [although I think you might have to make the variables public in that case... I'm not sure how visibility will affect it, as I haven't tried it before]
C: Use a different hydration Strategy or
D: Just have weird getter and setter names like getCat_name (please don't do this).
Quick and dirty...
foreach ($form as $key => $value) {
while ($pos = strrpos($key,'_')) {
$key = substr_replace($key, '', $pos, 1);
$capitalized = strtoupper($key[$pos]);
$key[$pos] = $capitalized;
}
$data[$key] = $value;
}
You can still use this trick:
/** #ORM\Column(name="column_name", type="string") */
protected $columnName;
function get(...);
function set($columnName){$this->columnName = $columnName}
Hope helps
Related
I have some doubt what to inject. Given this code:
class A
{
public function getSomething()
{
return 'something';
}
}
class TestMe
{
/**
* #var A
*/
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function greetings()
{
return 'Hello, '.$this->a->getSomething();
}
}
my test A:
function testA()
{
$a = new class() {
public function getSomething()
{
return 'aAnonimus';
}
};
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, aAnonimus');
}
testB, same but with mock:
function testA()
{
$a = $this->createMock(A::class);
$a->method('getSomething')->willReturn('bMockery');
$sut = new TestMe($a);
$this->assertEquals($sut->greetings(), 'Hello, bMockery');
}
in the first test I simply inject a plain object.
But the second its more Phpunit's way: using mocked objects.
Question is, for long period which one wins? I find the first more conviement, and for the 2nd test, you have to know the class name of dependency (otherwise you cant create a mock)
In the longterm, it's better the second way because it's better to have type hint in the constructor which will not allow you to provide a simple object.
Also when we are talking about UnitTests you should test a certain class without depending on 3rd party libraries or other services logic. So the best way is to use mocks for all of the services which are part of the tested class
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.
I follwed the following tutorial to understand how Doctrine works: http://doctrine-orm.readthedocs.org/en/latest/tutorials/getting-started.html
I now have a better understanding of Doctrine. However I am struggling to understand the entity manager and how to use it.
In the tutorial to get an instance of the entity manager, all you do is this:
$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config);
That is loaded in the bootstrap and is available through out the code base.
So my question is, how is this instantiated in ZF2?
EDIT: I have had some help on this via ZF Talk and the following was recommended to me. I am still however struggling to get it to work:
My goal is to pull an array of users from my database and output them to my view using Doctrines pagination class.
In my Controller for the given action I have:
public function usersAction() {
$userFunctions = $this->getServiceLocator()->get('Administration\Model\UserFunctionFactory');
$userArray = $userFunctions->getUsers();
$viewModel = new ViewModel(array('users' => $userArray));
return $viewModel;
}
The users function Model is as such:
namespace Administration\Model;
use Doctrine\ORM\Tools\Pagination\Paginator;
class UserFunctions
{
protected $em;
function __constructor(EntityManagerInterface $em) {
$this->em = $em;
}
public function getUsers()
{
$em = $this->em;
$dql = "SELECT * FROM Users";
$query = $em->createQuery($dql)
->setFirstResult(0)
->setMaxResults(100);
$paginator = new Paginator($query, $fetchJoinCollection = true);
$c = count($paginator);
foreach ($paginator as $post) {
$myArray[] = $post->getHeadline() . "\n";
}
return $myArray;
}
}
The factory for the SM:
<?php
namespace Administration\UserFunctionFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Administration\Model\UserFunctions;
class UserFunctionsFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
return new UserFunctions($em);
}
}
The module.config:
'service_manager' => array(
'factories' => array(
'Administration\Model\UserFunctionFactory' => 'Administration\Model\UserFunctionFactory')
),
I am getting the following error:
While attempting to create administrationmodeluserfunctionfactory(alias: Administration\Model\UserFunctionFactory) an invalid factory was registered for this instance type.
Your immediate issue (based on your code snippets, and the resulting error about the factory being invalid) is actually trivial.
Your module.config.php says the factory class is:
Administration\Model\UserFunctionFactory,
but the fully-qualified class name of the class defined in your factory's classfile is:
Administration\UserFunctionFactory\UserFunctionFactory
The namespace mismatch means the SM can't find your factory. So, your first fix is to make sure your factory is indeed defined in a file like Administration/src/Administration/Model/UserFunctionsFactory.php (assuming your module is using PSR-0), and change the first line to read namespace Administration/Model
this is what basically you should never do in a ZF2 controller
$userFunctions = new UserFunctions();
Instead you create a service (your UserFunctions) and get it in your controller using the service locator
namespace Administration\Service;
use Zend\ServiceManager\FactoryInterface,
Zend\ServiceManager\ServiceLocatorInterface;
use Administration\Model\UserFunctions;
class UserFunctionsFactory
implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new UserFunctions($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
}
}
So you user functions class constructor will be
use Doctrine\ORM\EntityManagerInterface;
// class declaration and props here
function __constructor(EntityManagerInterface $entityManager) {
$this->em = $entityManager;
}
Then you register the service
// file services.config.php
namespace Administration;
return array(
'factories' => array(
'admin.service.contact' => new Service\UserFunctionsFactory()
),
);
Please note that you can do injection using initializers more than use class constructor. I used this method in the above example for simplicity.
This is an example of an initializer that inject DoctrineEntityManager
namespace My\Service\Initializer;
use Zend\ServiceManager\InitializerInterface,
Zend\ServiceManager\ServiceLocatorInterface,
Zend\Debug\Debug;
use My\Service\EntityManagerAwareInterface;
class EntityManagerAwareInitializer
implements InitializerInterface
{
public function initialize($instance, ServiceLocatorInterface $serviceLocator)
{
if($instance instanceof EntityManagerAwareInterface) {
$instance->setEntityManager($serviceLocator->get('doctrine.entitymanager'));
}
}
}
and the interface
namespace My\Service;
use Doctrine\ORM\EntityManagerInterface;
interface EntityManagerAwareInterface
{
/**
* Set Doctrine 2 Entity Manager
*
* #param EntityManagerInterface $entityManager
*/
function setEntityManager(EntityManagerInterface $entityManager);
/**
* Get Doctrine 2 Entity Manager
*/
function getEntityManager();
}
and the registration of the initializer
'initializers' => array(
'My\Service\Initializer\EntityManagerAwareInitializer' => new EntityManagerAwareInitializer()
),
Please note that I have separated configuration files because my module implements some interfaces that gives the possibility to do so, for example
namespace My;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface,
Zend\ModuleManager\Feature\ControllerProviderInterface,
Zend\ModuleManager\Feature\ServiceProviderInterface,
Zend\ModuleManager\Feature\ConfigProviderInterface;
class Module
implements
AutoloaderProviderInterface,
ControllerProviderInterface,
ServiceProviderInterface,
ConfigProviderInterface
{
public function getConfig()
{
return include __DIR__ . '/../../config/module.config.php';
}
public function getControllerConfig()
{
return include __DIR__ . '/../../config/controllers.config.php';
}
public function getServiceConfig()
{
return include __DIR__ . '/../../config/services.config.php';
}
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/../../src/' . __NAMESPACE__,
),
),
);
}
}
My Module.php is inside my src, so pay attention to paths.
Hope it helped!
Doctrine2 has a built-in type 'array' that I find useful for my project. It perfectly works with arrays of scalar types. But now I want to use an array of objects. Something like this:
/**
* #ORM\Entity
*/
class MyEntity {
/**
* #var MyEntityParameter[] array of MyEntityParameter instances
*
* #ORM\Column(name="parameters", type="array", nullable=true)
*
*/
private $parameters;
}
Where MyEntityParameter is a class that can be serialized. I also use it in the Symfony's Form Builder.
My plan works perfectly, except that when the field in the MyEntityParameter instance gets changed, Doctrine doesn't detect it and thus doesn't update the record. If I delete or add array elements, Doctrine detects that. I realize that this happens because class instance object id doesn't change when I change its field, but then how can I make it so that Doctrine detects this change?
I found a working solution for me. I don't think it's that elegant, but in case if there are no good ways to solve this problem it can work for me and for others.
First of all, I decided not to keep objects in the array, but instead keep arrays. I, however, still want to use MyEntityParameter class in the Symfony's form builder. In this case, the idea is to disable mapping for our field:
In the form builder we do the following:
// Acme/Bundle/DemoBundle/Form/Type/MyEntityType.php
// ...
class MyEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('parameters', 'collection', array(
'mapped' => false, // do not map this field
'type' => new MyEntityParameterType(),
// ... other options ...
));
}
}
In the child type (MyEntityParameterType) we set 'data_class' to MyEntityParameter (I don't show the code, as it's not related to the problem).
Now all we need is to manually fill and process the data for this not-mapped field.
In the controller:
public function editAction($id, Request $request)
{
// ...
// $object is an instance of MyEntity
$form = $this->createForm(new MyEntityType(), $object);
$parameters = $object->getParameters();
if ($parameters) {
foreach ($parameters as $key => $parameter)
{
$form->get('parameters')->add($key, new MyEntityParameterType(),
array(
// here I assume that the constructor of MyEntityParameter
// accepts the field data in an array format
'data' => new MyEntityParameter($parameter),
)
);
}
}
if ($request->isMethod('POST')) {
$form->submit($request);
$parameters = array();
foreach ($form->get('parameters')->all() as $parameter) {
// here first getData() gives MyEntityParameter instance
// and the second getData() is just a method of MyEntityParameter
// that returns all the fields in an array format
$parameters[] = $parameter->getData()->getData();
}
$object->setParameters($parameters);
// if the parameters were changed in the form,
// this change will be detected by UnitOfWork
}
// ...
}
I remember coming across this issue before, and a workaround that worked for me was to set a new object so Doctrine would recognise the entity property has been modified, then set the object that has the changes you want persisted.
$parameters = $entity->getParameters();
$parameters->foo = "bar";
$entity->setParameters(new MyEntityParameter());
$entity->setParameters($parameters);
$em->persist($entity);
If it's an array of MyEntityParameter instances then the following code might work.
$parameters = $entity->getParameters();
$parameters[3]->foo = "bar"; // Just an example
$entity->setParameters(array(new MyEntityParameter()));
$entity->setParameters($parameters);
$em->persist($entity);
Let's say I have a small class that handles connections to MySQL databases:
class myDatabaseHandler{
private $_databases;
public function addDatabase($name, $dsn, $username, $password){
$this->_databases[$name] = array('dsn' => $dsn, 'username' => $username, 'password' => $password);
return true;
}
public function getDatabase($name){
$connectionInfo = $this->_databases[$name];
$database = new Database($connectionInfo['dsn'], $connectionInfo['username'], $connectionInfo['password']);
$database->doSomeSetup();
$database->attachMoreThings();
return $database;
}
}
I want to unit test these 2 methods:
class myDatabaseHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testAddDatabase(){
}
public function testGetDatabase(){
}
}
How can this I test those 2 methods? If I addDatabase(), at most it would return a Boolean telling me the operation succeeded. Since it writes to a private property, I cannot confirm that the correct data is indeed written to it.
I feel that using getDatabase() to get a Database object back and testing against it is not exactly ideal, because I would need to expose dsn, username and password just for the sake of testing. In addition, it is possible that the Database object might modify those values to a format it uses, so I need to store the original values just for testing.
What is the best way to approach this problem?
Testing certainly gets tricky when you try to construct and use an object in the same place. In this case, you are both constructing a Database and calling methods on it in your getDatabase($name) method. That makes it pretty much impossible to mock, for instance, and to get decent coverage your tests would need to test the functionality provided by the Database class to make sure the system was behaving as expected.
A better way might be using a proper factory as a dependency.
interface iDatabaseFactory
{
public function buildDatabase($dsn, $username, $password);
}
Then, you could mock both the database factory and the database instance itself to verify that it is both constructed correctly and initialized correctly:
class MockDatabaseFactory implements iDatabaseFactory
{
public $databaseParams = array();
public $databaseToReturn = NULL;
public function buildDatabase($dsn, $username, $password)
{
$this->databaseParams['dsn'] = $dsn;
$this->databaseParams['username'] = $username;
$this->databaseParams['password'] = $password;
return $this->databaseToReturn;
}
}
class myDatabaseHandlerTest extends PHPUnit_Framework_TestCase
{
public function testAddAndGetDatabaseUsesCorrectDbParameters(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
// implement MockDatabase according to your interface
$mockDatabase = new MockDatabase();
$mockDatabaseFactory->databaseToReturn = $mockDatabase;
$dbHandler.addDatabase("some name", "some dsn",
"some username", "pa$$w0rd");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertEquals($mockDatabase, $builtDatabase);
$dbParams = $mockDatabaseFactory->databaseParams;
$this->assertEquals("some dsn", $dbParams['dsn']);
$this->assertEquals("some username", $dbParams['username']);
$this->assertEquals("pa$$w0rd", $dbParams['password']);
}
public function testAddAndGetDatabaseInitializesDb(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
$mockDatabase = new MockDatabase();
$mockDatabaseFactory.setDatabaseToBuild($mockDatabase);
$dbHandler.addDatabase("name", "dsn", "user", "pass");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertTrue($mockDatabase->doSomeSetupWasCalled);
$this->assertTrue($mockDatabase->attachMoreThingsWasCalled);
}
}