Doctrine and MongoDb: Define required fields in MongoDb-Document ( - mongodb

I'm using MongoDB with Doctrine for a Symfony project.
For a document (I call it class Product) I need to define a field (e.g. name), but I want to act doctrine in a way that the document cannot be saved without defining a it. Meaning, whenever creating or updating a new Product, the property $name has to be defined.
For doctrine and MySql this is quite simple. There you have to say:
class Product {
...
/** #ORM\Column(type="string", nullable=false) */
private $name;
...
}
Is there a similar way in MongoDb?
The following code (which I found in a similar thread) doesn't work:
class Product {
...
/**
* #MongoDB\Field(type="string")
* #Constraints\NotBlank
*/
protected $name;
...
}

Related

Ordering embedded documents with Doctrine ODM

I’m trying to order my embed documents.
The field looks like this
/**
* #ODM\EmbedMany(targetDocument=Image::class, strategy="set")
* #ODM\Index(keys={"order"="asc"})
* #Groups({"offer:read"})
*/
protected $images = [];
The Image EmbeddedDocument
namespace App\Document\Embedded;
use App\Document\Traits\NameableTrait;
use App\Document\Traits\OrderableTrait;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
/**
* #ODM\EmbeddedDocument
*/
class Image
{
use NameableTrait;
use OrderableTrait;
…
}
And the orderable trait
namespace App\Document\Traits;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation\Groups;
trait OrderableTrait
{
/**
* #ODM\Field(type="int")
* #Groups({"offer:read"})
*
* #var int|null
*/
private $order;
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): void
{
$this->order = $order;
}
}
I updated the indexes with bin/console doctrine:mongodb:schema:update
However my Images are not ordered. Are the indexes the way to do it?
Index is not used for ordering documents in any way, they are telling the database to index the data so it can be searched through efficiently. In Doctrine's ORM there is an #OrderBy annotation but sadly it has not made its way to the ODM (yet). The solution would either be to support such thing natively in the ODM itself or you could use a custom collection for your embedded documents.
Here you will find documentation for custom collections and here's a link to my package that aims to kickstart your own implementations: https://github.com/malarzm/collections. To get what you need you will need your own collection class looking somehow like this:
class SortableCollection extends Malarzm\Collections\SortedCollection
{
public function compare($a, $b)
{
return $a->getOrder() <=> $b->getOrder();
}
}
and then plug it into your mapping:
/**
* #ODM\EmbedMany(targetDocument=Image::class, strategy="set", customCollection=SortableCollection::class)
* #ODM\Index(keys={"order"="asc"})
* #Groups({"offer:read"})
*/
protected $images = [];

Add refrences to MongoDB schema within Symfony project

I'm trying to create MongoDB database with some references within Symfony.
In my context I have 2 documents Customer and Meeting, One Customer can have Many Meeting so that what I did :
Meeting.php
<?php
namespace FrontOfficeBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class Meeting
{
/**
* #MongoDB\Id
* #MongoDB\ReferenceOne(targetDocument="Customer")
*/
protected $id;
/**
* #MongoDB\Field(type="timestamp")
*/
protected $creationDate;
...
Customer.php
<?php
namespace FrontOfficeBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class Customer
{
/**
* #MongoDB\Id()
* #MongoDB\ReferenceMany(targetDocument="Meeting")
*/
protected $id;
/**
* #MongoDB\Field(type="string")
*/
protected $username;
...
and then when I run the command line:
php bin/console doctrine:mongodb:schema:update
I got :
No identifier/primary key specified for Document 'FrontOfficeBundle\Document\Meeting'. Every Document must have an identifier/primary key.
I tried by using #MongoDB\UniqueIndex() but no way.
I think that #MongoDB\Id is supposed as an identifier !!!
Versions
Symfony 3.2
MongoDB 3.4.4
Any ideas ?
Thanks you.
Finally I found a solution, first I added a field called $meetings in the document Customer and an other $customer in the document meeting like this :
Customer.php
/**
* #MongoDB\ReferenceMany(targetDocument="meeting", mappedBy="customer")
*/
protected $meetings;
Meeting.php
/**
* #MongoDB\ReferenceOne(targetDocument="customer", inversedBy="meetings")
*/
protected $customer;
Then run the command line to generate setters and getters:
php bin/console doctrine:mongodb:generate:documents mybundleBundle
And when I run a fixtures (Creating one customer then its meeting), everything work fine, you'll notice that the relationship 1 to many has been defined in your document (I'm using compass to display mongoDB) as a One-to-Squillions model (for more details about 1-to-N relationship : (https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1) that's mean the son document (meeting) contains the reference of the parent as shown below :
Customer
Meeting
In MongoDB the #MongDB/Id is a primary key and the foreign key and never try to define references to this unique field.
Thanks for your attention.

How to get the type of MongoDB Document in twig?

I used Symfony2 and Doctrine MongoDBBundle and I have simple Single Collection Inheritance classes. How can I know what type of document it is in a twig template? For example the base class is Entity and extended by User and Organization, in listing those in a twig template I'd like to know what type of entity it is (i.e. whether it's a User or an Organization). I wonder if it's possible to get the value of the DiscriminatorField of the document.
/**
* #MongoDB\Document(collection="entity")
* #MongoDB\InheritanceType("SINGLE_COLLECTION")
* #MongoDB\DiscriminatorField(fieldName="type")
* #MongoDB\DiscriminatorMap({"user"="User", "shop"="Shop"})
*/
class Entity
{
/**
* #MongoDB\Id
*/
protected $id;
protected $entityType;
public function getEntityType()
{
return $this->entityType;
}
}

How to delete document from a referenced array of documents in doctrine ODM with mongodb

I have a php object mapping to a mongodb document(called Node) with a structure
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
class Node{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $domain;
/**
* #MongoDB\ReferenceMany(targetDocument="NodeItem",cascade=
* {"persist"},simple="true")
*/
protected $items = array();
//getter and setters below
}
And a referenced document called, NodeItem,
class NodeItem {
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $name;
/**
* #MongoDB\ReferenceOne(targetDocument="Node", cascade={"persist"},
* simple="true")
*/
protected Node;
//setter and getters
}
As reflected by the annotations above 'Node' references MANY 'NodeItems' stored in a $items array and 'NodeItems' references ONE 'Node'. So those are bi-directional referenced collections.
My Question is how to effectively delete a few 'NodeItem' documents from its collection (based on the array of available ids), so that the deleted NodeItem documents are also deleted from the $items array references in 'Node' (cascaded delete I think is what I am asking for?).
I wrote a function that has code like this :
$qb = $this->dm->createQueryBuilder('SomeBundleBundle:NodeItem');
/*
* deletes from NodeItem collection
*/
foreach($NodeItemsArray as $itemId){
$qb->remove()->field('id')->equals($itemId)->getQuery()->execute();
}
But the above function only deletes the documents from NodeItem collection, but the associated items in the $items array of 'Node' are not deleted. Also, the {cascade:persist} in the annotations doesn't seem to help. The code is implemented in Symfony 2 framework
Some help is appreciated !
The only way to achieve that is with a listener on the onRemove event.
But has mentioned by #jmikola, you'll have to use the $dm->remove() method, and not the QueryBuilder (since it doesn't support events yet).
so, to delete the Item do:
//Get the NodeItem you want in the items array and delete it:
$items = $node->getItems();
$dm->remove($items->get(2)); //Remove the third item as an example
And register the event:
class CascadeNodeDeleterListener {
public function preRemove(LifecycleEventArgs $eventArgs) {
$odm = $eventArgs->getDocumentManager(); /* #var $odm DocumentManager */
$object = $eventArgs->getDocument();
if($object instanceOf NodeItem) {
$node = $object->getNode();
$items = $node->getItems();
$items->removeElement($object);
$class = $dm->getClassMetadata(get_class($node));
$dm->getUnitOfWork()->recomputeSingleDocumentChangeSet($class, $node);
}
}
}
In services.yml:
<service id="listener" class="CascadeNodeDeleterListener">
<tag name="doctrine.common.event_listener" event="onRemove" />
</service>
See Doctrine ODM Events from more info.
Cascade behavior in ODM is only respected by UnitOfWork operations. MongoDB does not natively support cascades and triggers (yet, anyway). In your case, query builder will construct and execute a query like the following:
db.node_items.remove({"_id": ObjectId("...")})
UnitOfWork is not involved at all (there are no staged operations or flushing) and no cascade is triggered.
On the other hand, say you had a managed $nodeItem object. Passing it to DocumentManager::remove() would invoke UnitOfWork and cause any referenced documents mapped with cascade=REMOVE or cascade=ALL to also be removed. Of course, you'd have to call flush() to execute the operations in MongoDB.
Based on your current code, the only operation that will be cascaded is DocumentManager::persist(). In practice, I assume you'd create a Node, construct and add a few NodeItems to it, and persist the Node (allowing its items to be persisted automatically).
If NodeItems only ever belong to a single Node, you may want to avoid cascade=REMOVE and simply do $nodeItem->getNode()->getItems()->removeElement($nodeItem) after you remove the $nodeItem itself, but before flush().
Also, I noticed you're initializing your collection field to an array. Later on, ODM is going to hydrate this field as a Doctrine\ODM\MongoDB\PersistentCollection instance, which could lead to ambiguity. As a best practice, you should initialize such fields as Doctrine\Common\Collections\ArrayCollection in your constructor. That way, you can always expect them to be Collection instances.

is it possible to override doctrine2 persistentobject magic getters and setting

Can anybody tell me whether its posible to override doctrine2 persistentobject magic getters\setters? i'd like to do the below:-
public function setDob($dob)
{
$this->dob= new \Date($date);
}
however my entity is defined as:-
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Ajfit\Repository\User")
* #ORM\HasLifecycleCallbacks
*/
class User extends \Doctrine\Common\Persistence\PersistentObject
{
/**
* #var date $dob
*
* #ORM\Column(name="dob", type="date")
*/
protected $dob;
}
the public function setDob does not get called when I create the entity using:-
public function getNewRecord() {
return $this->metadata->newInstance();
}
I get the below error:-
Notice:- array to string conversion ...Doctrine\DBAL\Statement.php on line 98
Any help would be much apprieciated.
Thanks
Andrew
__call of PersistentObject#__call will not be called if you defined the setDob method.
What you're doing there is creating a new instance via metadata. What you are doing there is probably assuming that __construct or any setter/getter should be called by the ORM. Doctrine avoids to call any methods on your object when generating it via metadata/hydration (check ClassMetadataInfo#newInstance to see how it is done) as it does only know it's fields.
This allows you to be completely independent from Doctrine's logic.
About the notice, that is a completely different issue coming from Doctrine\DBAL\Statement, which suggests me that you have probably some wrong parameter binding in a query. That should be handled separately.