Version/Tracking single property of an entity - php

Lets say I'm working with an entity named Person. Person has properties such as height, weight, ect... that can be changed over time.
class Person
{
private $id;
private $weight;
private $height;
// etc...
}
I would like the User to be able to go back and see the changes over time on some form of graph. What is the best way to store each successive change for each separate property?
I've looked at the DoctrineExtension Loggable interface, but that creates a change entry for the entire object, so you can't 'browse' backwards by a specific property independent of the object, that has changed.
As well, reverting back to a previous version with Loggable will cause you to lose any changes between said version and current (as intended, but I want to be able to remove specific entries).
Would the best approach be a classic OneToMany/ManyToOne relationship from Person to "entry objects", such as PersonHeightEntry or PersonWeightEntry (which contain the value and a timestamp for the entry), pulling the most current timestamp as the current value?
Example:
class Person
{
// #ORM\OneToMany(...)
private $weight_entries;
}
class PersonWeightEntry
{
private $value;
private $timestamp;
// #ORM\ManyToOne(...)
private $person;
}

Actually, the Loggable Interface can be used to store the different versions of an object in the database while presenting the data in another form to the user. For example you can read the different heights from the database and present them with no need to show the whole object.
You can also "revert" changes for only one property by overwriting the property of the actual object with the old value which you get from the old version of that object.
Having many properties mapped with OneToMany relations will cause load on your database and make the computing heavier.
I would use the Loggable Interface, storing full objects in the database, and then alter the representation in your application according to your needs.

Related

Money\Money could not be converted to string

I am using Money/Money with Symfony and doctrine, but I am not sure how I should perform the mapping. Currently I use the following, resulting in this error message;
Error:
Money\Money could not be converted to string
Mapping:
/**
* #Groups({"group1"})
* #ORM\Column(type="string", nullable=true)
* #var Money
*/
private $price;
Internally Money/Money uses String as representation, thus I thought I could use it as well.
This happens because of doctrine type conversion. Before persisting, doctrine takes the values of your properties and transform them to SQL values using the type you specify in the column annotation. Since the type you are using for your price property is string, doctrine is trying to cast your Money object into a string.
You have many options to fix this, some of them simple and others not that much.
Simple but not optimal: Create a new Money class that will extend from the original one, and create __toString method. This will solve your problem persisting, but the property you'll get from the db will be a string, not an object. If you want to improve that, put some custom logic in your setter so you can create an instance of money from that value. Simple, but dirty.
Medium complex, but might not be what you need: You need to use a doctrine custom type. Is really not that hard. People get scared of this, but jump into the docs and you'll see how simple it is. It consists of basically creating a type, like "money" that contains instructions for doctrine on what to do before persisting properties of that type, and what to do after the value is fetched from the db. So you'll save it as a string in your database still, but you will also control the fetching, being able to create the Money instance. Now, depending on the structure of your money class, this might not be what you need.
Probably your best take: I imagine that your money class not only has the actual value, but maybe a currency type property. If that's the case, probably a Doctrine Embeddable is your best solution. With that you will be able to kinda separate the actual value and the currency code as separate fields in the database. Map your money class to be a Doctrine Embeddable (since is a class that lives outside of your domain logic, in vendor, you will have to use yaml or xml, and not annotations). When the object is fetched from the db, it will be an instance of the Money class. But for that to happen, you need to have proper getters and setters that the property access component can use. Best thing about embeddables, is that they are reusable with other entities.
Hope this info helps! Happy coding!

Doctrine with static entities

I have a database, where I store some fixed values like product categories. When I create a new product and I want to assign a category to it, I do it this way:
$categories = new ProductCategoryRepository();
$category = $categories->find(ProductCategory::EXAMPLE);
$product = new Product();
$product->setCategory($category);
However, I'm not sure why I have to lookup the database all the time to get static entities my app is already aware of.
It should be enough to assign the category statically. Maybe something like this:
$category = ProductCategory::EXAMPLE;
Now Doctrine should persist the relation with the correct ID (described by the ProductCategory class (which could be an entity?)) and I no longer have to lookup the database for static properties.
I don't know how to do this, yet. I could create new entities all the time, but this doesn't seem to be correct, because the values are already stored in the DB and they are always the same and not new entities.
$category = new ProductCategory::EXAMPLE;
Fetching the relation from the product however should return the property as an entity:
$category = $product->getCategory();
return $category instanceof ProductCategory; // true
Is there a way to achieve this behaviour?
It is more an architecture question than a performance tweak. I don't want to describe information multiple times (db entries, php constants, entity relations etc.).
There is something called "second level cache" in Doctrine, but the feature is considered experimental and you should maybe read the documentation carefully before using it.
A quote from the official documentation of this feature:
The Second Level Cache
The second level cache functionality is marked as experimental for now. It is a very complex feature and we cannot guarantee yet that it works stable in all cases.
Entity cache definition is done like this: (documentation)
/**
* #Entity
* #Cache(usage="READ_ONLY", region="my_entity_region")
*/
To improve performance for such entities like you are talking about in your question you should also consider to mark them as "read only", which will lead to performance increase from Doctrine 2.1, as can be found in the Doctrine documentation on improving performance:
Read-Only Entities
Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping references for details). This means that the entity marked as read only is never considered for updates, which means when you call flush on the EntityManager these entities are skipped even if properties changed. Read-Only allows to persist new entities of a kind and remove existing ones, they are just not considered for updates.
The entity should be configured like this: (documentation)
/** #Entity(readOnly=true) */
Second level cache and read only for your ProductCategory:
So after setting up second level read only caching with for example a region named read_only_entity_region your configuration for your ProductCategory would look something like this:
/**
* #Entity(readOnly=true)
* #Cache(usage="READ_ONLY", region="read_only_entity_region")
*/
class ProductCategory
{
//...your entity definition...
}
If you don't want it to hit the database every time you could just store it in the Cache:
public function getCategory(){
return Cache::rememberForever('category-'.$this->category_id, function() {
return $categories->find($this->category_id);
});
}
This will pull the info from the database if it has never been pulled, but will just grab it from the cache if it has been. You would have to use Cache::forget('category-2') to remove it, or php artisan cache:clear. Your static values would just be integer IDs and your products would have a category_id but the categories themselves would be cached.

Doctrine ORM - How to persist static properties

I have been using Doctrine ORM for a while an now, I have a class-level property (static property) which I need to persist in MySQL database and I would like to now how.
Class Student {
private $name;
public static $instances = array();
public __construct($name) {
$this->name = $name;
self::$instances[] = $this->name;
}
}
According to the Documentation (Basic Mapping > Property Mapping):
The next step after marking a PHP class as an entity is mapping its properties to columns in a table.
To configure a property use the #Column docblock annotation. The type attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, string is used as the default.
It sounds like doctrine only supports object-level properties. But as the title reads "Basic Mapping", I think there should be some type of "Advanced Mapping" that maybe covers static properties. I searched for that with no success.
Also it is not listed at Limitations and Known Issues
Question
Someone please let me know if this is possible to persist static properties in Doctrine 2, and if not, How should I accomplish this task? Any work arounds or something?
Unfortunately, my reputation does not allow commenting, so I have to write this as an answer. Sorry for that.
My first thought is that you are going about this the wrong way. Can you give some more information what the "instances" actually are? It looks like it's a list of all student names in the system? Why do you need to have this as a static class property?
Some thoughts:
1)
It sounds like doctrine only supports object-level properties.
Yeah, I think that's correct, but I don't see the use case for anything else really. To me it feels intuitively wrong to have Static data in a database as it's not meant to change (very) often. Can't you just have your values in the code or in a config file (if there are too many). If, on the other hand, your data is changing often, then it's not Static (<=> not changing).
2) If you really want to map it and have it in the db I believe association mappings is the way to go (one-to-many or many-to-many). I.e., you should move it to its own Entity that has a relation to the Student entities. In your case it looks like you should create a University entity that has a list of all students. And that way you can iterate to build a list of all names.

Design objects with save/delete/update functionality

I am using Cassandra with heavy denormalization so I cannot use some kind of universal class to delete/add/update/etc.. objects since every type of object has its own list of tables that needs to be changed.
For example to delete User I will need to touch 3 tables, not just one. To delete Item I will need to touch 7 tables, etc.. It means that logic is completely different based on object type.
Scenario 1
User class contains only fields that I need (id, name, etc..) and static functions to find users, delete users etc..
<?php
class User {
private $id;
private $name;
// Magic getters and setters removed to save space here
public static function delete($id) {
// find user by id
// delete that user
}
}
Scenario 2
User class has everything - fields (id, name, etc..) and also functions that will delete/edit/create/etc.. that particular user
<?php
class User {
private $id;
private $name;
// Magic getters and setters removed to save space here
public function delete() {
// find user by $this->id
// delete that user
}
}
Which scenario is better and maybe there is some other way to do this that is even better?
I vote #2.
The main thing is to choose one, be clear why and stick to it. Things are going to get very confusing if you mix these approaches or you are not clear about why you have chosen a particular approach.
I Scenario 2 because its clear what you are deleting, its always related to $this.
Also in Scenario 2 your delete method needs less validation as this validation can be offloaded to the constructor, or simple check if $id is set before deleting the object or database row.
In Scenario 1 though you would need to take $id and check that is exists before attempting to remove it in order to be sure you are actually removing something. You could also return the number of rows deleted too, which could be validation in itself too. But in the future this validation may be more complex than just checking what is being deleted.
Better to let the construction or a load() function deal with as much of the validation.

Managing objects with multiple contexts

Question:
Is there a preferred design pattern for handling an object under different contexts? For example: if I need to GET a user from the database then that object needs to have an id and a privilege level. However, if I want to create a NEW user for registration then it does not need an id since this will be added in the database via auto_increment. A GUEST user does not need an id but should have a username of 'Guest'.
Attempted Solutions
Using a constructor seems to neglect context.
Creating new objects for registered_user, new_user, and guest_user seems wasteful.
Creating methods within the user object that can be used to initialize it in unique ways is tempting.
As of now I believe that I should create a separate factory that has functions such as create_guest($x), create_new_user($x, $y), create_registered_user($x, $y, $z)? This would allow the user object to have one purpose: hold a user (while still allowing it to have a constructor to establish a bare minimum requirement), and also have a user factory with the sole purpose of: initiating different types of users.
Please excuse any ignorance, just looking for the best design pattern for handling the same object in unique contexts.
Example:
<?php
class user
{
private id;
private username;
private privilege;
public function __construct()
{
some code...
}
public function is_admin()
{
some code...
}
}
?>
Using the provided class, you can always autoinitialize the value for $this->id to be 0 and use it to determine if this user is a guest. Main idea is that you will never have an id of 0 in your database (if you are using auto-increment for this column).
This can also be used to check if you are to update or create the record. A simple check on the id would reveal either 0 or another int. If it is 0, then it is either guest or it should be created. If it is greater than 0, it has already been created and it needs to be updated.
Generally, I prefer to pack the create and update into the user class itself by adding load(), update(), create() and delete() methods, where load() would accept an array/object which if passed will be used to load the data into the current context or if not supplied, the function will try to read the data from a different source (such as a DB).

Categories