Managing Data
In n2n's object-oriented approach we manage data using business objects. In this chapter we introduce you to working with business objects!
Creating Business Objects
Entities are objects that serve the management of data. Business objects are entities that, besides the data, can also contain logic for data processing. In our blog module we will work with three business objects: BlogArticle, BlogCategory, BlogComment. Please note that it is not about creating a great blog module, but about demonstrating as much of n2n's functionality as possible with little code.
For our business objects we create a separate directory in our module: /app/qs/bo
Business objects consist of their properties and the corresponding getter and setter methods. The only thing that is a bit more complicated to understand are the annotations of the relationships between the business objects.
business-objects.zip
Download the business objects and copy them into the directory of the business objects
Properties and Relationships
First take a closer look at the imported business objects.
BlogArticle
A BlogArticle can have several comments (OneToMany relationship) and several categories (ManyToMany) assigned. These relationships are annotated in the business object:
// annotation of the relationships:
private static function _annos(AnnoInit $ai) {
$ai->p('comments', new AnnoOneToMany(BlogComment::getClass(), 'blogArticle', CascadeType::ALL));
$ai->p('comments', new AnnoOrderBy(array('id' => 'DESC')));
$ai->p('categories', new AnnoManyToMany(BlogCategory::getClass(), null,
CascadeType::PERSIST|CascadeType::MERGE));
}
The associated comments and categories are available as properties ($comments, $categories). For relationships the properties that contain the associated objects are annotated.
For the comments (BlogComments) a OneToMany relationship is annotated, because several comments can fall upon one BlogArticle. The following parameters are passed to AnnoOneToMany:
- The class of the associated object
- The property of the associated object where the article is stored
- The CascadeType determines which operations (
PERSIST,MERGE,REMOVE,DETACH) are passed on to the associated objects. In our case all operations (CascadeType::ALL) are passed on. This means that when the blog article is deleted (REMOVE), the operation is passed on to the associated comments and they are deleted as well!
The second annotation of comments determines that the comments, when they are called via getComments(), are always sorted by id DESC.
The third annotation determines the relationship to BlogCategory. We define a Many-To-Many relationship. Here we leave the second parameter (mappedBy) empty and only pass on PERSIST and MERGE for the CascadeType. Because REMOVE is missing, the associated categories are not deleted when an article is deleted. That makes sense, because other blog articles could still be associated with these categories!
BlogCategory
On BlogCategory too, the annotation of the relationship to BlogArticle stands out:
private static function _annos(AnnoInit $ai) {
$ai->p('articles', new AnnoManyToMany(BlogArticle::getClass(), 'categories',
CascadeType::PERSIST|CascadeType::MERGE));
}
Here we now see the other end of the Many-To-Many relationship between article and categories. In contrast to the annotation on BlogArticle, the second parameter of AnnoManyToMany is not NULL here. On one side of a Many-To-Many relationship we have to specify a mappedBy property.
BlogComment
On BlogComment we now see a Many-To-One relationship. It is the other end of the One-To-Many relationship on BlogArticle:
private static function _annos(AnnoInit $ai) {
$ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass(), CascadeType::MERGE|CascadeType::PERSIST));
$ai->p('image', new AnnoManagedFile());
}
The annotation on the property $image indicates that the property is a file. AnnoManagedFile() ensures that a FileManager takes care of storing the uploaded file.
That the property $image is something special can also be seen in the setter method:
public function setImage(File $image = null) {
$this->image = $image;
}
Here it is determined that the passed value must be a File. To also allow NULL values, the = null is important.
Another peculiarity we find in the constructor:
public function __construct() {
$this->setCreated(new \DateTime());
}
With this instruction you set the default value of the property created to the current date and the corresponding time. Every comment that is created gets the current date as its created value this way.
Next we want to take a closer look at the annotations of the relationships.
Annotations of Relationships
n2n knows the following classes to annotate relationships between business objects:
AnnoOneToMany(1:n relationship)AnnoManyToOne(n:1 relationship)AnnoManyToMany(n:n relationship)AnnoOneToOne(1:1 relationship)
The first three classes are explained in more detail below.
1:n Relationship
This annotation is located on the class BlogArticle. A BlogArticle can have several BlogComments. The class AnnoOneToMany knows the following important parameters in the constructor:
| Property | Explanation | Value / example |
|---|---|---|
$targetEntity | The class of the object to which a relationship exists. | BlogComment::getClass() |
$mappedBy | The property on which the other object stores the relationship. | 'blogArticle'BlogComment has the property $blogArticle, where the relationship is stored. |
$cascadeType | Here it is defined which operations are passed on. | CascadeType::ALLWith all database manipulations these are transferred to the attached objects. Attention: this includes deletion! |
$fetchType | Here it is determined when the associated objects should be loaded. Immediately when loading the object or only on access. | FetchType::EAGER, loads all associated objects immediatelyFetchType::LAZY, loads the objects only when they are needed. |
$orphanRemoval | Determines whether objects that are no longer linked should be deleted. | true or false |
CascadeType
We want to take a closer look at $cascadeType. The CascadeType determines which operations are passed on to the associated objects. There are the following important types:
CascadeType::PERSIST(create)CascadeType::MERGECascadeType::REMOVE(delete)CascadeType::ALL
Here we look at PERSIST and REMOVE more closely. When an object is persisted, with CascadeType::PERSIST the associated objects are persisted as well. With REMOVE an object is deleted. CascadeType::REMOVE accordingly means that all associated objects are deleted as well! With CascadeType::ALL all operations are passed on.
Attention
If a business object is mistakenly given
CascadeType::REMOVEorCascadeType::ALL, associated objects are deleted as well. This can lead to data loss!
n:1 Relationship
This annotation is located on BlogComment. A BlogComment always belongs to one BlogArticle. The class AnnoManyToOne has the following important constructor parameters:
| Property | Explanation | Value / example |
|---|---|---|
$targetEntity | The class of the object to which a relationship exists. | BlogArticle::getClass() |
$cascadeType | Determines which operations are passed on to the associated objects. | `CascadeType::MERGE |
$fetchType | Here it is determined when the associated objects should be loaded. |
In contrast to the 1:n relationship there is no $orphanRemoval here.
n:n Relationship
The last relationship we look at more closely is the n:n relationship. We find it on BlogArticle and BlogCategory. A BlogArticle can be assigned to several BlogCategory and vice versa. The annotation AnnoManyToMany has the following important constructor parameters:
| Property | Explanation | Value / example |
|---|---|---|
$targetEntity | The class of the object to which a relationship exists. | BlogArticle::getClass() |
$mappedBy | The mappedBy value only has to be specified on one side of the relationship. This way the system decides which class is master and which is slave. | null or the property on the other object |
$cascadeType | Determines which operations are passed on to the associated objects. | `CascadeType::MERGE |
$fetchType | Here it is determined when the associated objects should be loaded. | |
$orphanRemoval | Determines whether objects that are no longer linked should be deleted. |
SQL Dump
With this the business objects we need in the course of this Quickstart are defined and their relationships explained. For storing the business objects the corresponding tables in the database are still needed. n2n uses a predefined naming strategy. It derives the corresponding table and column names from the names of classes and properties. Example: BlogArticle objects are stored in the table blog_article.
Attention
Via annotations table (
AnnoTable) and column names (AnnoColumn) can be chosen individually. n2n even allows the definition of your own naming strategy.
Database tables can also be created via the Hangar. In the Quickstart we create the tables with the following SQL file.
quickstart.sql
Registering Entities
All entities must be registered in n2n. To do so, add the following lines to var/etc/qs/app.ini:
[orm]
entities[] = "qs\bo\BlogArticle"
entities[] = "qs\bo\BlogCategory"
entities[] = "qs\bo\BlogComment"
From now on n2n knows our business objects. Now we are ready to access our data!