Configurable dynamic Doctrine database entities in Symfony 2 - php

What I am trying to achieve
Users would be able to configure Doctrine entities through an HTML form on a website.
Users would be able to define new entities, as well as add and delete fields for existing entities. (Similar to Drupal's content types)
The Doctrine entities would get dynamic properties based on the configuration that the user supplied through the web UI.
Either the single DB table per Doctrine entity would be altered dynamically whenever an entity configuration changes; Or there could be multiple tables used per single entity (each new entity field would get its own table).
Done so far
I have been researching this for the past few days without much success but I stumbled across this answer which seems quite related to what I am trying to achieve.
I have registered and added the loadClassMetadata listener which maps the field foo:
// src/DynamicMappingTest/AdminBundle/EventListener/MappingListener.php
namespace DynamicMappingTest\AdminBundle\EventListener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if ($classMetadata->getName() != 'DynamicMappingTest\\AdminBundle\\Entity\\CustomNode')
{
// Not the CustomNode test class. Do not alter the class metadata.
return;
}
$table = $classMetadata->table;
$oldName = $table['name']; // ... or $classMetaData->getTableName()
// your logic here ...
$table['name'] = 'custom_node';
$classMetadata->setPrimaryTable($table);
$reflClass = $classMetadata->getReflectionClass();
dump($reflClass);
// ... or add a field-mapping like this
$fieldMapping = array(
'fieldName' => 'foo',
'type' => 'string',
'length' => 255
);
$classMetadata->mapField($fieldMapping);
}
}
Now, this all works as long as I have the foo property declared in the DynamicMappingTest\AdminBundle\Entity\CustomNode class:
// src/DynamicMappingTest/AdminBundle/Entity/CustomNode.php
namespace DynamicMappingTest\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* CustomNode
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="DynamicMappingTest\AdminBundle\Entity\CustomNodeRepository")
*/
class CustomNode
{
...
private $foo;
}
Problem
However, there is no way for me to know what properties the users will define for their custom entities. If I remove the foo property from the CustomNode class, the ReflectionClass that I get from the ClassMetadata will naturally not include the foo property and so I get the following exception whenever the mapField() in MappingListener is executed:
ReflectionException: Property DynamicMappingTest\AdminBundle\Entity\CustomNode::$foo does not exist
in vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php at line 80
77. */
78. public function getAccessibleProperty($class, $property)
79. {
80. $reflectionProperty = new ReflectionProperty($class, $property);
81.
82. if ($reflectionProperty->isPublic()) {
83. $reflectionProperty = new RuntimePublicReflectionProperty($class, $property);
Questions
Is it possible to have fully configurable dynamic Doctrine entities?
Am I on the right track with my approach? If not, could you suggest an alternative?
How could I have truly dynamic class properties? Or should I be generating new Doctrine entity PHP classes whenever the users change the entity configuration?

Is it possible to have fully configurable dynamic Doctrine entities?
Doctrine generates proxy classes for you entities. That means that doctrine generates PHP code with class, which extends your Entity class and overrides the methods - puts some custom logic and then calls the parent method.
So, I think that the only way to make this really happen is to generate the PHP code for entities in your code. That is, every time entity is created in your website, you should generate PHP file with that entity, then run migrations.
Am I on the right track with my approach? If not, could you suggest an alternative?
I don't think that you should use Doctrine ORM at all in this case, at least in the way you're trying to do that.
Generally, ORM is used for easier/more manageable programming. That is, you can set relations, use lazy-loading, unit of work (change entity properties and then just flush) etc. If your entities are generated dynamically, what features will you use at all? Developer will not write code for these entities, because, as you've said, there is no way to know what fields it will have.
You haven't provided concrete use-case - why do you want to do that in the first place. But I imagine that it could be really done in some easier way.
If users can store any structure at all, should you use MySQL at all? ElasticSearch or similar solutions could be really much better in such cases.
How could I have truly dynamic class properties? Or should I be generating new Doctrine entity PHP classes whenever the users change the entity configuration?
As I've mentioned - yes. Unless you would want to override or replace some of Doctrine code, but I imagine it could be lots of it (proxy classes etc.)

Related

Symfony 5 referencing a field within a referenced table

The question hard to formulate in just one sentence when I don't have the exact terms ready for use, but I'm basically working on a Symfony 5 project that involves a MySQL database. I use Twig to allow for communication between my PHP controllers and my HTML interface. Until now, I've been doing just fine using simple references to entity fields in Twig, such as:
myEntity.someField
To get the value I needed. However, I currently need to reference a "nested" field like so :
myEntity1.myEntity2Field.someField
The "nesting" making a world of a difference between the two. I am now getting an error when trying to do this (Impossible to access an attribute ("someField") on a string variable ("<value from entity2 field>")),
probably because my database is not organized correctly yet, from what I understand. Hopefully you could understand my difficulty. So, how can I tweak my database to allow this sort of double-referencing to take place?
Note: myEntity2Field refers to the name of a field from Entity2 that should serve as a reference to the entire Entity2 table, from which someField can then be extracted.
the error you are getting is because you are trying to access a value from a string or integer, not an Object.
myEntity.someField is the same as $myEntity->getSomeField() if you have a getter Method or $myEntity->someField if you are accessing the property directly.
If someField value is not an Object you can only access the value but if its an Object you will be able to access the other object properties and methods in this way.
To do so the best way is to use association(relation) between your entities spically if you have the association already in the database and if you build your relations right in your database so you jaut have to refernce this relation or association between your entities and doctrine will do the rest for you.
as an example:
<?php
/** #Entity */
class User
{
// ...
/**
* #ManyToOne(targetEntity="Address")
* #JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
}
/** #Entity */
class Address
{
private $street;
public function getStreet(){
....
}
}
this example is a ManyToOne relation between the user and the addresse entity with the getters methos in the user entitiy you will be able to use the nesting when you access the user:
$user->getAddress()->getStreet()
as an example and this will work in twig also:
{{user.address.street}}
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/association-mapping.html#many-to-one-unidirectional
the other way you can get the object inside your calss and map it with methods so you can access it as you want.
i hope i will be able to help you with this info.

Symfony get nested entities which are not flagged as deleted

I am programming my first symfony project and I stuck at a problem:
I have a entity I called it "load" which refers to other entities which a call "transaction" (x transactions belongs to 1 load) :
/**
* #ORM\OneToMany(targetEntity="Transaction", mappedBy="load")
*
*/
private $transactions;
The transactions can be flagged as deleted but they keep stored in the database.
I created a custom repository and some methods which deliver me the undeleted transactions.
But if I want to fetch a load with all it's transactions, I call
$load = $loadRepository->find($id);
it does what it is supposed. It fetches all transactions from the database which are referenced to the given load.
But I don't want to have the deleted transactions in my result. How can I achive this? I have absolutely no approach. Sure, I can iterate over the transactions and remove the deleted ones but I think there is a better solution.
I tried to register a repository for the transactions (this works) and override the find-method (this doesn't work).
Is there another method which is internally called and I have to override?
thank you in advance!
If I have correctly understand your question, this behavior already exist with the doctrine extension SoftDeletable, you can find documentation here
You just have to add something like deleteAt property on your Transaction entity then all entities with data inside this field will be automatically filtered like if they are really deleted. If you want to find all of the Transaction entities you can always disable the filter on a query.
I found a solution:
#config.yml:
doctrine:
orm:
filters:
deletedFilter:
class: AppBundle\Filter\SoftDeletedFilter
enabled: true
_
namespace AppBundle\Filter;
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class SoftDeletedFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if(!is_subclass_of($targetEntity->rootEntityName, "AppBundle\Entity\SoftDeleteableInterface")){
return "";
}
return $targetTableAlias.'.deleted = 0';
}
}
Now, all entities which implements the (empty) interface "SoftDeleteableInterface" will not be included if the deleted-value is not 0.
Thank you anyway for the answers/comments

Using Laravel 4 models in custom classes / subsystems without class dependency

I'm building a parser system that will parse loads of different XML/JSON feeds upon request/cronjob.
I use Laravel 4.
The purpose of the thread is to use IoC in my context, and not hardcoded Model names in custom-class methods
Providing an example of parser for Soccer Player with XML structure like:
<players category="Midfielders">
<player id="777">
<name>Caio Augusto Paim do Santos</name>
<statistic>
<club name="CamaƧari" id="7191" league="Baiano 2" league_id="1136" season="2013" minutes="" appearences="" lineups="" substitute_in="" substitute_out="" substitutes_on_bench="" goals="" yellowcards="" yellowred="" redcards=""/>
I've created an additional directory in my /app folder called /parsers These are custom classes, they all extend or implement custom abstracts/interfaces in the same folder and basically are responsible for receiving path to XML/JSON file and returning a well-structured PHP arrays.
They are added in composer.json in autoload as: "app/parsers"
Screenshot of file structure attached
All is good and the code/classes are testable and not dependent on another classes, but here's the problem.
Checkout the XML example, there's thing like:
<club id="XXX" league_id="YYY" />
this is feed club id and feed league id, but I have my own IDs in database referenced to feed IDs.
Like on this screenshot:
So the logic says: Go to database, check if there's id in league league table with feed_id provided from XML file.
If yes, get it, if not, create a new league and get the id for future references.
This requires me to use Model in my parser classes, now I know you can use IoC and inject models into Controllers, but I'm not sure I can do the same with my parser classes...
So doing something like this in the middle of my parser class:
// Try to get league and season ids from database if they already exists, if not, insert
$leagueId = DB::select('SELECT id FROM league WHERE feed_id=?', array($data['league_id']));
or
$league = new LeagueModel();
Is pretty much incorrect.
Now just to clarify the way it all works, my parsers are getting called in Laravel Command classes like this:
/**
* Execute the console command.
*
* #return void
*/
public function fire()
{
$this->setParser();
$this->setStorage();
$this->parser->parseFile($file);
}
And Laravel Command classes are getting called in my Controllers like:
$stamps = $this->getStamp();
Artisan::call('command:getSoccerPlayer',array('stamps' => $stamps, 'parser_id' => Request::segment(2)));
The Controller itself is called via URI:
/jobs/soccer_player/parse?type=soccer&directory=players
**What do you suggest or how would you overcome this issue to avoid dependencies and still use Models for interactions with the database in this context? **
P.S Please don't pay attention that the whole parse logic on my screenshot is in the same method "parse" now, I will break it into pieces once I see the full picture of how I want it to work/look.
Appreciate any help!
you can still call your namespaced models
use App\Models\League;
class SoccerPlayerParser extends AbstractParser{
//...
public function parse()
{
//...
$league = App\Models\League::find($data['league_id']);
//...
}
//....
}
I see two possible solutions here, but am not 100% sure how to integrate it in your project.
The first is to store the name of the model class to use in a config file and intantiate the model via new $class where $class is a value retrieved via Config::get or similar. This solution is very common in packages, and even Laravel itself uses it (see the model setting in app/config/auth.php).
The other is to not instantiate the model, but instead create an interface for it and then dependency-inject it into your command. You can easily auto-inject stuff into your commands by using Artisan::resolve('MyNamespace\MyCommand') instead of Artisan::add(new MyCommand), and then inject via type hinting as you do via controllers. http://laravel.com/docs/ioc#practical-usage
Once you've set up the interface as an argument to the command's constructor, you can use App::bind('MyInterface', 'MyModel') to tell Laravel which class to inject, and this can be swapped at any point.

Custom Collection in Doctrine2

Just starting to work with Doctrine2, and am wondering how/if I can use a custom collection class. Searches point me to this part of the documentation:
Collection-valued persistent fields and properties must be defined in terms of the Doctrine\Common\Collections\Collection interface. The collection implementation type may be used by the application to initialize fields or properties before the entity is made persistent. Once the entity becomes managed (or detached), subsequent access must be through the interface type.
While I'm sure that's crystal clear to someone, I'm a little fuzzy on it.
If I setup my Entity to initialize (say in __construct()) the collection variable to a class that implements the correct interface - will Doctrine2 continue to use that class as the collection? Am I understanding that correctly?
Update: Also, I gather from various threads that the placeholder object used in lazy loading may influence how a custom collection can be used.
Let me try to clarify what is possible, not possible and planned with examples.
The quote from the manual basically means you could have the following custom implementation type:
use Doctrine\Common\Collections\Collection;
// MyCollection is the "implementation type"
class MyCollection implements Collection {
// ... interface implementation
// This is not on the Collection interface
public function myCustomMethod() { ... }
}
Now you could use it as follows:
class MyEntity {
private $items;
public function __construct() {
$this->items = new MyCollection;
}
// ... accessors/mutators ...
}
$e = new MyEntity;
$e->getItems()->add(new Item);
$e->getItems()->add(new Item);
$e->getItems()->myCustomMethod(); // calling method on implementation type
// $em instanceof EntityManager
$em->persist($e);
// from now on $e->getItems() may only be used through the interface type
In other words, as long as an entity is NEW (not MANAGED, DETACHED or REMOVED) you are free to use the concrete implementation type of collections, even if its not pretty. If it is not NEW, you must access only the interface type (and ideally type-hint on it). That means the implementation type does not really matter. When a persistent MyEntity instance is retrieved from the database, it will not use a MyCollection (constructors are not invoked by Doctrine, ever, since Doctrine only reconstitutes already existing/persistent objects, it never creates "new" ones). And since such an entity is MANAGED, access must happen through the interface type anyways.
Now to what is planned. The more beautiful way to have custom collections is to also have a custom interface type, say IMyCollection and MyCollection as the implementation type. Then, to make it work perfectly with the Doctrine 2 persistence services you would need to implement a custom PersistentCollection implementation, say, MyPersistentCollection which looks like this:
class MyPersistentCollection implements IMyCollection {
// ...
}
Then you would tell Doctrine in the mapping to use the MyPersistentCollection wrapper for that collection (remember, a PersistentCollection wraps a collection implementation type, implementing the same interface, so that it can do all the persistence work before/after delegating to the underlying collection implementation type).
So a custom collection implementation would consist of 3 parts:
Interface type
Implementation type (implements interface type)
Persistent wrapper type (implements interface type)
This will not only make it possible to write custom collections that work seemlessly with the Doctrine 2 ORM but also to write only a custom persistent wrapper type, for example to optimise the lazy-loading/initialization behavior of a particular collection to specific application needs.
It is not yet possible to do this but it will be. This is the only really elegant and fully functional way to write and use completely custom collections that integrate flawlessly in the transparent persistence scheme provided by Doctrine 2.
No, whenever Doctrine gives you back an implementation of the Doctrine\Common\Collections\Collection interface, it will be a Doctrine\ORM\PersistentCollection instance. You cannot put more custom logic on the collection. However that is not even necessary.
Say you have an entity (Order has many OrderItems), then a method to calculate the total sum of the order should not be located on the collection, but on the Order item. Since that is the location where the sum has meaning in your domain model:
class Order
{
private $items;
public function getTotalSum()
{
$total = 0;
foreach ($this->items AS $item) {
$total += $item->getSum();
}
return $total;
}
}
Collections however are only technical parts of the ORM, they help to implement and manage references between objects, nothing more.
Same question here, with a reference to the official doctrine Jira issue page with the details and status of this 'feature'... You can keep track of the development there!

Should I be extending this class? (PHP)

I'm creating an ORM in PHP, and I've got a class 'ORM' which basically creates an object corresponding to a database table (I'm aiming for similar to/same functionality as an ActiveRecord pattern.) ORM itself extends 'Database', which sets up the database connection.
So, I can call: $c = new Customer();
$c->name = 'John Smith';
$c->save();
The ORM class provides this functionality (sets up the class properties, provides save(), find(), findAll() etc. methods), and Customer extends ORM. However, in the future I may be wanting to add extra public methods to Customer (or any other model I create), so should this be extending ORM or not?
I know I haven't provided much information here, but hopefully this is understandable on a vague explanation, as opposed to posting up 300+ lines of code.
I agree with the other answers here - put the additional methods into a descendant class. I'd also add an asterisk to that though: each time you extend the class with extra methods, think about what you are trying to achieve with the extension, and think about whether or not it can be generalised and worked back into the parent class. For example:
// Customer.class.php
function getByName($name) {
// SELECT * FROM `customer` WHERE `name` = $name
}
// ** this could instead be written as: **
// ORM.class.php
function getByField($field, $value) {
// SELECT * FROM `$this->table` WHERE `$field` = $value
}
You're certainly thinking correctly to put your business logic in a new class outside your 'ORM'. For me, instead simply extending the ORM-class, I'd rather encapsulate it with a new, value object class to provide an additional degree of freedom from your database design to free you up to think of the class as a pure business object.
Nope. You should use composition instead of inheritance. See the following example:
class Customer {
public $name;
public function save() {
$orm = new ORM('customers', 'id'); // table name and primary key
$orm->name = $this->name;
$orm->save();
}
}
And ORM class should not extend Database. Composition again is best suited in this use case.
Yes, place your business logic in a descendant class. This is a very common pattern seen in most Data Access Layers generation frameworks.
You should absolutely extend the ORM class. Different things should be objects of different classes. Customers are very different from Products, and to support both in a single ORM class would be unneeded bloat and completely defeat the purpose of OOP.
Another nice thing to do is to add hooks for before save, after save, etc. These give you more flexibility as your ORM extending classes become more diverse.
Given my limited knowledge of PHP I'm not sure if this is related, but if you're trying to create many business objects this might be an incredibly time consuming process. Perhaps you should consider frameworks such as CakePHP and others like it. This is nice if you're still in the process of creating your business logic.
You're definitely thinking along the right lines with inheritance here.
If you're building an ORM just for the sake of building one (or because you don't like the way others handle things) than go for it, otherwise you might look at a prebuilt ORM that can generate most of your code straight from your database schema. It'll save you boatloads of time. CoughPHP is currently my favorite.
I have solved it like this in my Pork.dbObject. Make sure to check it out and snag some of the braincrunching I already did :P
class Poll extends dbObject // dbObject is my ORM. Poll can extend it so it gets all properties.
{
function __construct($ID=false)
{
$this->__setupDatabase('polls', // db table
array('ID_Poll' => 'ID', // db field => object property
'strPollQuestion' => 'strpollquestion',
'datPublished' => 'datpublished',
'datCloseDate' => 'datclosedate',
'enmClosed' => 'enmclosed',
'enmGoedgekeurd' => 'enmgoedgekeurd'),
'ID_Poll', // primary db key
$ID); // primary key value
$this->addRelation('Pollitem'); //Connect PollItem to Poll 1;1
$this->addRelation('Pollvote', 'PollUser'); // connect pollVote via PollUser (many:many)
}
function Display()
{
// do your displayĆ­ng for poll here:
$pollItems = $this->Find("PollItem"); // find all poll items
$alreadyvoted = $this->Find("PollVote", array("IP"=>$_SERVER['REMOTE_ADDR'])); // find all votes for current ip
}
Note that this way, any database or ORM functionality is abstracted away from the Poll object. It doesn't need to know. Just the setupdatabase to hook up the fields / mappings. and the addRelation to hook up the relations to other dbObjects.
Also, even the dbObject class doesn't know much about SQL. Select / join queries are built by a special QueryBuilder object.

Categories