I'm Using Symfony2 with the DoctrineMongoDB Bundle. I made a service that receives Informations in JSON Format (Objects).
The Object I'm sending got a property for referencing to another Object in a different collection in the Database.
Changing the reference works. But in case I send another field, like "title" with the ObjectB - it sets the title to the new value in the DataBase. How can I prevent this?
I just want to set the new reference, no manipulation on that Object what so ever.
Here is some Code (shortened)
class Fun{
/**
* #MongoDB\Id(strategy="auto")
*/
private $id;
/** #MongoDB\EmbedMany(targetDocument="JokeEmbedded", strategy="set")
*/
private $jokes = array();
}
class JokeEmbedded
{
/**
* #MongoDB\ReferenceOne(targetDocument="JokePattern", cascade={"persist"})
*/
private $ref;
/**
* #MongoDB\String
*/
private $title;
}
class JokePattern
{
/**
* #MongoDB\Id(strategy="AUTO")
*/
private $id;
/**
* #MongoDB\String
*/
private $title;
}
I'm now sending the following JSON to the Service: (JSON represents the ObjetClass Fun)
[{"id":"1","jokes":[{"ref":{"id":"222", "title":"new title"}]]
My question is now, how do I ignore the new given "title" for the reference I want to Set?
I want to have the new reference in the DB set to the ID 222. Nothing more.
Any Help would be great!
Thank you!
Edit:
This is the code that handles the JSON Input
$request = $this->getRequest();
//Get JSON-Data
$data = $request->getContent();
$funs = $this->get('serializer')->deserialize(
$data,
'ArrayCollection<Acme\FunBundle\Document\Fun>',
'json'
);
//create documentmanager
$dm = $this->get('doctrine_mongodb')->getManager();
foreach ($funs as $obj) {
//save to db
$dm->persist($obj);
}
$dm->flush();
I managed it with the deserialization context and the list annotation within the JMS SerializerBundle.
Greetings :)
Related
I am facing an error with symfony and mongoDB odm on one to one relationship
for example i have a user that has Work .
User Class:
/**
* #MongoDB\Document
* #MongoDBUnique(fields="email")
*/
class User implements UserInterface
{
/**
* #MongoDB\Id
*/
private $id;
/**
* #MongoDB\Field(type="string")
*/
private $firstName;
/**
* #MongoDB\Field(type="string")
*/
private $lastName;
/**
* #MongoDB\Field(type="string")
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #MongoDB\ReferenceOne(targetDocument=Work::class)
*/
private $work;
//getter setter
Work Class:
class Work
{
/**
* #MongoDB\Id()
*/
private $id;
/**
* #MongoDB\ReferenceOne(targetDocument=User::class)
*/
private $user;
//getter setter
}
Controller:
class TestingController extends AbstractController
{
/**
* #Route("/testing", name="testing")
* #param DocumentManager $documentManager
* #return Response
* #throws MongoDBException
*/
public function index(DocumentManager $documentManager)
{
$user = new User();
$user->setFirstName('test1');
$user->setLastName('test2');
$user->setEmail('test123#gmail.com');
$documentManager->persist($user);
$work= new Work();
$work->setUser($user);
$documentManager->persist($work);
$documentManager->flush();
return new Response("test");
}
/**
* #Route("/test", name="test")
* #param DocumentManager $documentManager
* #return Response
*/
public function test(DocumentManager $documentManager){
$user = $documentManager->getRepository(User::class)->findAll();
dump($user);
return new Response("test test");
}
}
So I created 2 classes one as user that has one work, I created the user , then I created a work and i assigned the user from the work class.
in MongoDB compass I got under Work collection a reference for the user.
now in the test method in the controller i try to find the users and dump the data.
The problem is whenever i want to find $user->getWork() i get a null value, while the user exists. but the inverse is working fine . whenever i try $work->getUser() i can find the user.
is there anything wrong in my code ? I want to use both methods : $user->getWork() and $work->getUser(),
I have tried adding to the ReferenceOne mappedBy and inversedBy but its always one of the two methods returns null value.
I think you forgot the mappedBy and inversedBy arguments in the annotation. See the documentation: https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/2.1/reference/bidirectional-references.html#one-to-one
I know that similar questions have been asked here on SO, but answers don't satisfy me.
Having following:
Standard form with fields foo, bar, baz, bat, x
DTO (not entity, dummy object, no annotations)
Constraints on some fields attached using FormBuilder
Form is used in multiple places.
In some places that form is used, I would like to add single validator to fields foo, bar, baz. This validator can get only values of those fields or whole propagated DTO. It should have access to DI Container to call service that will check data against database.
For now I'm thinking about one of two solutions:
Adding extra constraint to the form in controllers that require it (sound dirty)
Adding extra field to form constructor/DTO (not boolean but business logic that tells if extra validation is needed) and adding extra constraint to form.
Problem is that I can't figure out how to handle either of those cases.
Finally, I want to emphasize that I don't want to use validation groups and annotations - both will add extra dependencies and logic to DTO.
Found the answer. For short answer you can check out This Matt Daum post.
Here's full example, presenting not only how to create custom form validator, but also how to inject services and extra data to form (because that was my case).
If you want simple receipe, go straight to the bottom.
Let's have DTO:
class MyFormDTO
{
/** #var string */
private $name;
/** #var string */
private $surname;
/** #var string */
private $phone;
/** getters and setters ommited */
}
Now, define dependencies in form. First two are services, last one (Calendar) is some extra data needed from controller.
class MyForm extends AbstractType
{
(fields hidden)
/**
* #param Sender $sender
* #param TranslatorInterface $translator
* #param Calendar $calendar
*/
public function __construct(Sender $sender, TranslatorInterface $translator, Calendar $calendar)
{
$this->translator = $translator;
$this->sender = $sender;
$this->calendar = $calendar;
}
}
Now there are two ways - if you only need services in your form, you can just define your form as a service. If you, like me, need extra data, you need to write form factory service:
class MyFormFactory
{
(fields hidden)
/**
* #param Sender $sender
* #param TranslatorInterface $translator
*/
public function __construct(Sender $sender, TranslatorInterface $translator)
{
$this->sender = $sender;
$this->translator = $translator;
}
/**
* #param Calendar $calendar
*
* #return MyForm
*/
public function getMyForm(Calendar $calendar)
{
return new MyForm($this->sender, $this->translator, $calendar);
}
}
Let's define this factory as a service with right dependencies:
mybundle.form.myform_factory:
class: MyBundle\Service\FormFactory\MyFormFactory
arguments: [ #text_message.sender, #translator ]
How to get the form in controller? Easy as that:
class MyController extends Controller
{
/**
* #ParamConverter("calendar", options={"mapping"={"calendarId":"id"}})
*
* #param Request $request
* #param Calendar $calendar
*
* #return Response
* #throws Exception
*/
public function myAction(Request $request, Calendar $calendar)
{
$formDTO = new MyFormDto();
$myForm = $this->get('mybundle.form.myform_factory')->getMyForm($calendar);
$form = $this->createForm($myForm, $formDTO);
(handling post hidden)
}
}
And now the most important part - we have services injected properly into our form. How to use them and validate selected data? like this:
class MyForm extends AbstractType
{
(fields hidden, constructor shown in previous example)
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var MyFormDTO $myDTO */
$myDTO = $options['data'];
(build form as usual, using services and data from $options and $this->calendar injected by controller and factory)
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults([
'csrf_protection' => true,
'constraints' => [
new Callback(function (MyFormDTO $data, ExecutionContextInterface $context) //notice that we have access to fully propageted DTO here
{
//use injected service
$isValid = $this->sender->validateSomething($data->getSurname(), $data->getPhone());
if (false === $isValid)
{
$context
->buildViolation($this->translator->trans('wrong_surname_phone_pair'))
->addViolation();
}
return $isValid;
})
],
]);
}
}
I'd like to use one text form field to save data in two entity properties. However I didn't found any solution/hint which might lead to a clean implementation.
Consider the following example†:
There is a Doctrine entity with two properties: count, unit
A user enters "34 apples" in a simple text input field
The DataTransformer (or whatever is appropriate) should transform this value in such a way that in the underlying entity count = '34' and unit = 'apple' is being saved.
you could have a field which is not mapped in the database and in the prePersist and preUpdate methods you would transform the data and save these in the appropriate fields. For example:
/**
*
* #ORM\HasLifecycleCallbacks()
*/
class MyClass
{
private $info;
/**
* #ORM\Column(name="count", type="integer")
*/
private $count;
/**
* #ORM\Column(name="unit", type="string")
*/
private $unit;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function separateInfo(){
$infoArray = explode(' ', $this->info);
$this->count = $infoArray[0];
$this->unit = $infoArray[1];
}
}
And in the form you only add the 'info' field:
class MyClassType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('info');
}
}
This is not tested, but it's the idea which could help you.
I hope this be useful for you.
Kind regards.
I have to Entities
class Patients
{
/**
* #ORM\OneToOne(targetEntity="ContactAddress", mappedBy="patients")
*/
protected $contactaddress;
}
And another
class ContactAddress
{
/**
* #ORM\OneToOne(targetEntity="Patients", inversedBy="contactaddress")
* #ORM\JoinColumn(name="patient_id", referencedColumnName="id")
*/
protected $patient;
}
When I try execute this code
$em = $this->getDoctrine()->getEntityManager();
$product = $em->getRepository('SurgeryPatientBundle:Patients')->find($id);
I get
No mapping found for field 'patients' on class 'Surgery\PatientBundle\Entity\ContactAddress'.
When i tried get to Contact repository I get result
Please Help ;D
Sorry for my English
You must refer to patient instead of patient*s* in ContactAddress entity.
class Patients
{
/**
* #ORM\OneToOne(targetEntity="ContactAddress", mappedBy="patient")
*/
protected $contactaddress;
}
I'm writing a feature which calls for the records of my joining table to carry extra metadata (Joining-Table with Metadata). I've attempted to implement this in accordance with this section of the Doctrine documentation.
See below for example Entity definitions.
The challenge now is that getGroups and setGroups do not yield/set Group entities (& the same is true from the Group instance perspective), but they yield GroupUser entities.
This adds a substantial delay to process of managing this relationships, which so far have been extremely smooth - for example, I cannot simply add, remove, or check for the existence of a Group to the collection which getGroups yields.
Can anyone identity any errors in my implementation, or else recommend a more fluid way of implementing this concept?
Thanks in advance for any input.
EDIT:
My main concern is this: using this implementation, retrieving a collection of Users from a Group entity requires this Entity method's mediation:
public function getUsers() {
return $this->users->map(function($groupUser){
return $groupUser->getUser();
});
}
I'm concerned that this could imply a major performance hit down the road. Am I incorrect?
Furthermore, how does one re-implement the setUsers method?
Group entity:
<?php
/**
* #Entity
* #Table(name="group")
*/
class Group {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="group")
* #var \Doctrine\Common\Collections\Collection
*/
protected $users;
}
User entity:
<?php
/**
* #Entity
* #Table(name="user")
*/
class User {
/**
* #Column(type="integer", nullable=false)
* #Id
*/
protected $id = null;
/**
* #OneToMany(targetEntity="GroupUser", mappedBy="user")
* #var \Doctrine\Common\Collections\Collection
*/
protected $groups;
}
Joining entity:
<?php
/**
* #Entity
* #Table(name="group_user")
*/
class GroupUser {
/**
* #Id
* #ManyToOne(targetEntity="User", inversedBy="groups")
* #JoinColumn(name="userId", referencedColumnName="id")
*/
protected $user;
/**
* #Id
* #ManyToOne(targetEntity="Group", inversedBy="users")
* #JoinColumn(name="groupId", referencedColumnName="id")
*/
protected $group;
/**
* #Column(type="integer")
*/
protected $relationship;
}
Related -
Same goal, slightly different approach, which consistently produced errors once the resulting collections were manipulated: http://www.doctrine-project.org/jira/browse/DDC-1323
Supports the approach, no technical details: Doctrine 2 join table + extra fields
I've found just two examples (see question) of entity definitions for this specific type of relationship, however no example code for how they're used. As such it was fairly unclear how fluid (or otherwise) the resulting setters & getters could be expected to be. Hopefully this code will help clear up the approach for anyone else making a similar attempt.
The ideal solution under the circumstances (thanks #doctrine # freenode) was to implement a custom repository - a more flexible & efficient place for creating & managing the association.
Example Custom Repository for Join-Table with Metadata Class - Solution accompanies code in original question
<?php
use Doctrine\ORM\EntityRepository;
class GroupUserRepository extends EntityRepository {
/**
* #param \User $user
* #param \Group $group
* #param integer $type One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUserToGroup(User $user, Group $group, $relationship, $role = '') {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if(!$groupUser) {
$groupUser = new GroupUser();
$groupUser->setGroup($group);
$groupUser->setUser($user);
$groupUser->setRole($role);
$groupUser->setRelationship($relationship);
$this->_em->persist($groupUser);
}
return $groupUser;
}
/**
* #param \User $user
* #param \Group $group
* #return null
*/
public function removeUserFromGroup(User $user, Group $group) {
$groupUser = $this->findOneBy(array('user' => $user->getId(), 'group' => $group->getId()));
if($groupUser)
$this->_em->remove($groupUser);
}
}
Then, from the join-table class, modify the Entity meta-data accordingly to specify the custom repository.
<?php
/**
* #Entity(repositoryClass="\Path\To\GroupUserRepository")
*/
class GroupUser {
// ...
}
This causes the custom repository to yield in place of the default one, making a proxy method from the Entity class simple.
<?php
/**
* #Entity
*/
class Group {
/**
* #param \User $user
* #param integer $relationship One of the integer class constants defined by GroupUser
* #param string $role Optional string defining user's role in the group.
* #return \GroupUser
*/
public function addUser(User $user, $relationship, $role = '') {
return $this->_em->getRepository('GroupUser')
->addUserToGroup($user, $this, $relationship, $role);
}
}
And things are about as manageable as they were before.