On-demand load for ReferenceMany objects - php

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;
...
}

Related

What is the best way to set/update association lookup field inside Doctrine entity

I have the main entity
/**
* #ORM\Entity()
*/
class Document
{
/**
* #var int
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var DocumentStatus
* #ORM\ManyToOne(targetEntity="DocumentStatus")
*/
private $status;
/**
* #var string
* #ORM\Column(type="text")
*/
private $text;
}
and the lookup "enum" entity (seeding on application deploy)
/**
* #ORM\Entity(repositoryClass="DocumentStatusRepository");
*/
class DocumentStatus
{
const DRAFT = 'draft';
const PENDING = 'pending';
const APPROVED = 'approved';
const DECLINED = 'declined';
/**
* #var int Surrogate primary key
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string Natural primary key (name for developers)
* #ORM\Column(type="string", unique=true)
*/
private $key;
/**
* #var string Short name for users
* #ORM\Column(type="string", unique=true)
*/
private $name;
/**
* #var string Full decription for users
* #ORM\Column(type="string", nullable=true, unique=true)
*/
private $description;
}
with simple repository
class DocumentStatusRepository extends EntityRepository
{
public function findOneByKey($key)
{
return parent::findOneBy(['key' => $key]);
}
}
I want to encapsulate domain logic of document lifecycle by intoducing methods like
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getByKey(DocumentStatus::DRAFT);
}
public function approve()
{
try {
$this->doSomeDomainActions();
$this->status = $something->getByKey(DocumentSatus::DRAFT);
} catch (SomeDomainException($e)) {
throw new DocumentApproveException($e);
}
}
...
or
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getDraftDocumentStatus()
}
public function approve()
{
$this->status = $something->getApprovedDocumentStatus()
}
...
without public setters. Also I want keep Document loose coupling and testable.
I see the next ways:
Permanent inject DocumentStatusRepository (or service that encapsulates it) into every instance by constructor on entity creation and by using public setter in postLoad subscriber
Permanent inject DocumentStatusRepository by static Document method on application bootstrap or on loadClassMetadata
Temporary inject DocumentStatusRepository in constructor and methods like Document::approve
Use setStatus() method with complex logic based on $status->key value
Encapsulate document domain logic in some DocumentManager and use Document entity like simple data storgae or DTO :(
Are there other ways? Which way is easier and more convenient to use in the long term?
Using generated Identities for Document.
Now you generate the identity on the side of the database. So you save Document from the domain perspective in inconsistent state. Entity/Aggregate should be identified, if it has no id it shouldn't exists.
If you really want to keep to database serials, add method to the repository which will generate id for you.
Better way is to use uuid generator for example ramsey/uuid.
And inject the id to the constructor.
DocumentStatus as Value Object
Why Document Status is Entity? It does look like a simple Value Object.
Then you can use Embeddable annotation. So it will leave within same table in the database, no need for doing inner joins.
DocumentStatus gets behaviour for example ->draftDocumentStatus(), which returns NEW DocumentStatus with draft status, so you can switch the old instance one with new one. ORM will do the rest.
DocumentStatusRepository
If you really want to keep DocumentStatus as entity, which in my opinion is wrong you shouldn't have DocumentStatusRepository.
Document is your aggregate root and the only entrance to the DocumentStatus, should be by aggregate root.
So you will have DocumentRepository only, which will be responsible for rebuilding the whole aggregate and saving it.
Also you should change the mapping.
It should have FETCH=EAGER type, so it will retrieve DocumentStatus with Document together.
Secondly you should do mapping with CASCADE=ALL and ORPHANREMOVAL=TRUE.
Otherwise, if you remove Document, DocumentStatus will stay in the database.

Doctrine 2 OneToOne, load owning side only when neccessary

I have two entities - User and UserSettings. In User entity, I want to have UserSettings as an attribute. That would be OK, I would add a OneToOne relation but there's a problem - because UserSettings is an owning side of the relation, every time I load User entity, Doctrine has to load the UserSettings entity too.
Is there a way how to load User but not UserSettings?
I made maybe a weird solution - there's no relation between these entities and the settings are loaded by method of Facade. For example:
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/** #var UserSettings */
private $settings;
public function __construct()
{
$this->settings = new UserSettings();
}
public function setSettings(UserSettings $settings)
{
$this->settings = $settings;
}
}
/**
* #ORM\Entity
*/
class UserSettings
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="user_id", type="integer")
*/
private $userId;
}
class UserFacade
{
/**
* #var EntityManager
*/
private $em; // is injected automatically by DI
public function loadSettings(User $user)
{
$settings = $this->em->getRepository("UserSettings")->findOneBy(array("userId" => $user->id));
$user->setSettings($settings);
}
}
$user = $em->find("User", 1);
// if I want user's settings
$userFacade->loadSettings($user); // now I can use $user->getSettings()->something;
Side note: UserFacade is a service class that manipulates with users' data like adding new user, editing, deleting etc. In my MVC application, controller classes communicate with Facades, not with EntityManager directly.
That's OK - settings are loaded only when I want to. However, there are two possible problems:
a) I don't think this is a clear way
b) When I want a list of users, I cannot JOIN a table where settings are, because entities are not associated, so I have to make an extra SQL for each user.
My question is - how to solve the problem with OneToOne relation? I don't have much experience with Doctrine, so it may be a stupid question - sorry for that.
Thanks!

Doctrine 2 : Page, Block, Strategy pattern & related entity

My problem is exactly that described in the Strategy Pattern article in Doctrine documentation :
A Page entity
A page can have some blocks
A block might be a text, an image, a form, a calendar, ... (strategy)
A page knows about blocks it contains, but doesn't know about their behaviours
Block inheritance is not possible
Described solution (Strategy pattern) seems exactly what I need (read article for further information) :
Page:
<?php
namespace Page\Entity;
class Page
{
/**
* #var int
* #Id #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #var string
* #Column
*/
protected $title;
/**
* #var string
* #Column(type="text")
*/
protected $body;
/**
* #var Collection
* #OneToMany(targetEntity="Block", mappedBy="page")
*/
protected $blocks;
// ...
}
Block:
<?php
namespace Page\Entity;
class Block
{
/**
* #Id #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #ManyToOne(targetEntity="Page", inversedBy="blocks")
*/
protected $page;
/**
* #Column
*/
protected $strategyClass;
/**
* Strategy object is instancied on postLoad by the BlockListener
*
* #var BlockStrategyInterface
*/
protected $strategyInstance;
// ...
}
Strategy interface:
<?php
namespace Page\BlockStrategy;
interface BlockStrategyInterface
{
public function setView($view);
public function getView();
public function setBlock(Block $block);
public function getBlock();
public function renderFrontend();
public function renderBackend();
}
I can easily imagine what would be my strategy if I would display a form or a calendar;
but what if my strategy is to display content of an other entity ?
The block needs to know about entity class/id and has to be deleted when related entity is removed.
I imagined to add entityClass and entityId properties in Block and load related entity on the postLoad event in a BlockListener.
But what if related entity doesn't exist ? I can't remove the block in postLoad.
So, I imagined to create an other listener watching for removal of related entity and remove refering Block in that listener.
But it means that I need to add a listener for every entity that can be put in a block.
It could work, but it seems very complicated... maybe someone has a better idea ?
I'm not sure if I understood well your question, but if what you want is to have entities inside entities and delete the child entities when the father is deleted, you could also treat the entity as a block and use a Composite pattern.
You basically could use the same interface on the entity and the blocks, and on the entity the display function could be something like:
foreach ($block in $blocks) {
$block->display();
}
For deleting all the children when you delete the parent entity, you could simply do it on the destructor of the entity.
function __destruct() {
foreach ($block in $blocks) {
/* call a common interface function that does all that need to be done, implemented on each block */
}
}
For more on Composite pattern:
http://en.wikipedia.org/wiki/Composite_pattern

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

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

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();

Categories