How to store a document inside another document, with Doctrine ODM? - php

How to store a document inside another document, with Doctrine ODM?
I don't see an Array or Json type in the documentation.
I would like to be able to do something like this:
class Post {
/**
* #MongoDB\String
*/
protected $body;
/**
* #MongoDB\Array
*/
protected $comments = array();
}
I don't want to have a separate collection for comments. I want them saved inside each post.

/**
* #MongoDB\Document
*/
class Post
{
/**
* #MongoDB\Id
*/
private $id;
/**
* #MongoDB\String
*/
private $body;
/**
* #MongoDB\EmbedMany(targetDocument="Comment")
*/
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
}
/**
* #MongoDB\EmbeddedDocument
*/
class Comment
{
/**
* #MongoDB\String
*/
private $body;
}
But note that comments are not good candidates for embedding — contrary to probably the most popular example of embeds in MongoDB. I started with comments as embeds too, but then run into some problems and decided to store them in a separate collection. I don't remember all the problems, but the main one was the inability to sort comments on the database side. The quick solution was to sort them on the client side, but when it comes to pagination, it just doesn't scale.

I think this is what you're looking for: http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/reference/embedded-mapping.html

In my __construct() I need
new \Doctrine\Common\Collections\ArrayCollection();
where you just have
new ArrayCollection();

Related

What's the best design for extra data on behaviour class?

I have a game where the player can finish some tasks.
I have separated the behaviour part of the task to its ORM part.
Eventually a copy of the task is being saved somewhere on the player's document (doesn't matter where for this specific question).
The problem is, I am not sure where to put the extra information that I send to the client that is not necessary for the behaviour itself, but it is needed to show the player information regarding the task itself.
This is my task interface:
interface ITask
{
/**
* #param Player $player
*/
public function init(Player $player);
/**
* #param PlayerAction $action
*/
public function progress(PlayerAction $action);
public function reset();
/**
* #return bool
*/
public function isComplete();
}
This is my abstract task:
abstract class BaseTask implements ITask
{
/**
* #var int
*/
public $id;
/**
* #var int
*/
protected $currentValue;
/**
* #var int
*/
protected $targetValue;
public function __construct($targetValue)
{
$this->currentValue = 0;
$this->targetValue = $targetValue;
}
/**
* #param int
*/
public abstract function setCurrentValue($current);
/**
* #return int
*/
public abstract function getCurrentValue();
/**
* #return int
*/
public abstract function getID();
/**
* #param int
*/
public abstract function setID($id);
/**
* #return int
*/
public abstract function getTargetValue();
/**
* #param int
*/
public abstract function setTargetValue($target);
/**
* #return boolean
*/
public function isComplete()
{
if ($this->getCurrentValue() >= $this->getTargetValue())
{
return true;
}
return false;
}
}
Now I need to decide how where to put the extra data, e.g description, title, theme etc...
I thought about two options: I can just put it on the base task
itself, but then what happens if I don't need it? I just leave it
blank? feel like the wrong place for me.
I could create a wrapper
class that will hold the task, but then I will need to always
call the wrapper to get to the task, and it feels kind of
wrong.
Looking for alternative suggestions.
You should inherit the CustomTask from TaskBase.
If you you have limitation in inheritance, encapsulate additional fields into a class called TaskAdditionalInfoBase and associate to the TaskBase.
Then various classes can inherit TaskAdditionalInfoBase to present a custom additional info to the the task.

Zend Framework 2, making the model query a web service

I'm completely new to Zend framework (though I've already used Symfony2 and I've heard they're similar), and I've started a project in which I have to upgrade a site (that is already fully functional) that was created with Zend 1.11.
The aim of my upgrade is to allow data (that was originally stored in a database, and that will now be stored in nosql, and a database, and could be in the future stored elsewhere) to be more buildable and less strongly coupled with Zend's model. (Model as in the M of MVC).
In order to achieve this, I was asked to use a web service that would interact with the data, and Zend's model.
That way, when the data's structure would be modified, the Zend website wouldn't directly be impacted, (and would still work!) and we'd just have to re-arrange the web service.
Is there any elegant way to make Zend's model interact with a web service rather than a database?
I hope my question is understandable...
Have a nice day,
M.G.
You can use Data mapper pattern. As a reference, you can see how the module ZfcUser has adopted this pattern
You can create an interface of mapper for each entity and create an implementation according to the the data storage.
For example,
Product Entity
class Product
{
/**
* #var int
*/
protected $id;
/**
* #var string
*/
protected $name;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return UserInterface
*/
public function setId($id)
{
$this->id = (int) $id;
return $this;
}
/**
* Get name.
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set name.
*
* #param string $name
* #return UserInterface
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
}
Product Mapper
<?php
<?php
namespace Product\Mapper;
interface ProductMapperInterface
{
/**
* #var int $id
* #returns \Product\Entity\Product
*/
public function findById($id);
/**
* #var array $criteria
* #returns \Product\Entity\Product[]
*/
public function find(array $criteria, .....);
/**
* #var \Product\Entity\Product $product
*/
public function insert($product);
/**
* #var \Product\Entity\Product $product
*/
public function update($product);
}
To populate the entity, you can use hydrators. As a reference, you can view how ZfcUser uses hydrators.

On-demand load for ReferenceMany objects

I am new to the ORM but already impressed about its possibilities.
When designing Facebook-like messaging system (message threads allowing multiple user chat) I have come to a problem which I don't know how to solve.
After I load the MessageThread with DocumentManager::find(...) I get the whole MessageThread object with all the messages. This might not be a good idea because of memory limitations.
So my question is whether there is a way, how to load messages dynamically, on-demand so I get the MessageThread object but when accessing the messages property, they get loaded dynamically perhaps in bundle of 50 messages?
Thank you.
These are User, Message and MessageThread classes.
Using Doctrine MongoDB ODM
class User {
/** #Id */
protected $id;
}
class Message
{
/** #Id */
protected $id;
/** #ReferenceOne(targetDocument="User") */
protected $sender;
/** #String */
protected $body;
/** #Date */
protected $sent;
/** #EmbedMany */
protected $read;
}
class MessageThread
{
/** #Id */
protected $id;
...
/** #ReferenceMany(targetDocument="User") */
protected $participants;
//Maybe EmbedMany is better in this case
/**
* #ReferenceMany(
* targetDocument="Message",
* )
*/
protected $messages;
...
}

Doctrine 2.1 - entity inserting

I have question about inserting entity into a database. I have two models:
class News {
/**
* #Column(type="string", length=100)
* #var string
*/
protected $title;
/**
* #ManyToOne(targetEntity="User", inversedBy="news")
* #JoinColumn(referencedColumnName="id")
*/
protected $author;
}
class User {
/**
* #Id #GeneratedValue #Column(type="integer")
* #var integer
*/
protected $id;
/**
* #OneToMany(targetEntity="News", mappedBy="author")
*/
protected $news;
public function __construct() {
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
}
To add new news I must include both User and News classes (if they're in separate files, for ex. UserModel.php and NewsModel.php) and write a code:
$news = new News()
$news->setTitle('TEST title');
$news->setAuthor($database->find('User', 1));
$database->persist($news);
My question is: Is there any way to insert news without including User class?
You don't need to actually load the User.
Instead, you can use a reference proxy:
<?PHP
$news = new News()
$news->setTitle('TEST title');
$news->setAuthor($em->getReference('User',1));
$em->persist($news);
one other thing you could do (thinking in a more object-oriented kinda way) is add a method called addNews($news) on your user entity:
public function addNews($news) {
// you should check if the news doesn't already exist here first
$this->news->add($news);
$news->setAuthor($this);
}
and add cascade persist to your mapping:
/**
* #OneToMany(targetEntity="News", mappedBy="author", cascade={"persist"})
*/
protected $news;
then fetch your user, add the news, and merge the changes:
$news = new News()
$news->setTitle('TEST title');
$author = $database->find('User', 1);
$author->addNews($news);
//merge changes on author entity directly
$em->merge($author);
I preferr this approach because it gives you the opportunity to do extra checks or controls while adding the news, making for reusable and easy to read code

Docblocks for Doctrine collections

Is there a standard way to document the expected class of entities inside a Collection in the docblock comment in a Doctrine project? Something like:
/**
* #var Collection<User>
*/
protected $users;
Looks like PHPDoc is the de-facto standard for docblock annotations now, but I couldn't find any mention for this use case.
Here is a solution that enables you to have autocompletion both on the Collection methods and your objects methods:
/**
* #param Collection|User[] $users
*/
public function foo($users)
{
$users-> // autocompletion on Collection methods works
foreach ($users as $user) {
$user-> // autocompletion on User methods work
}
}
It works like a charm in PhpStorm at least.
UPDATE 2022
Doctrine now documents the generic types of Collection with the #template syntax, which is nowadays supported by PhpStorm & static analysis tools (Psalm & PHPStan) at least:
/**
* #var Collection<int, User>
*/
protected Collection $users;
The old PhpStorm hacky way of documenting this, Collection|User[] is no longer required.
There are a few different ways to document expected variables. Have a look at the phpDoc documentation for a full list of available tags.
class MyClass
{
/**
* Users collection
* #var \Doctrine\ORM\ArrayCollection
*/
protected $users;
/**
* My method that doesn't do much
* #param \Doctrine\ORM\ArrayCollection $users
* #return void
*/
public function myMethod(\Doctrine\ORM\ArrayCollection $users)
{
/** #var \Entities\Users $user */
$user = current($this->users);
}
}
I think User[] should work. Don't remember where I found that.

Categories