Is there a way to override the default behavior of SS Data Objects such that when I assign a static $table_name property to my DataObject the dev/build does not create a table name with the DO name like it normally does?
For example I have this very small Data Object
<?php
class SalesRep extends DataObject {
private static $table_name = 'tbl_users';
}
I am trying to prevent creation of table salesrep on dev/build and also I would like the ORM to know that when I do a $Model->write(); I'm writing to the table tbl_users instead of table salesrep
This is currently not possible with SilverStripe 3.x. SilverStripe uses the "convention over configuration" principle and the database tables always have the same name as the related DataObject.
However, in SS4, with namespacing, you'll be able to define a tablename in your config. As #bummzack already noted, this is currently in alpha.
However, you might try and overwrite DataObject's getBaseTable(), which method like:
/**
* Get the name of the base table for this object
*/
public function baseTable() {
return 'tbl_users';
}
but i doubt it'll work without problems, cause in other places the baseTable property is - again - generated out of the class names.
This is part of using the ORM that is within SilverStripe and can take some getting used to. I would perhaps look at this in two different ways...
1) If your goal is to present a certain name to the user, but have a different table name then the solution is to use singular_name and plural_name and then you are free to name the DataObject however you wish...
class tbl_users extends DataObject {
private static $singular_name = 'Sales Rep';
private static $plural_name = 'Sales Reps';
...
}
..remember the whole point of the ORM is that the PHP class defines the table and it would make sense to keep the table name the same as you'd like to use in the code.
2) If it absolutely has to be a specific table then you can specify it as an external table/content and one of the following solutions might suit you best... "Save to external Table", "External Content Module" or "External Data Module"
Related
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.)
I'm using PHP 5.4. I'm in the process of trying to make my application more SOLID. I'm currently going through my objects and making sure they follow SRP. I'm stuck on how to handle populating my object with properties, especially properties that "extend" the object's properties. Let me better explain.
I have a class called Flock (yes, a group of chickens--I'm in agriculture). It consists of several properties that are in the flocks table in the database: id, member_id, integrator_id, date_placed, date_picked_up, etc:
<?php
class Flock
{
private $id;
private $member_id;
private $farm_id;
private $integrator_id;
private $comments;
private $production_sq_ft;
private $number_of_houses;
private $avg_effective_age_of_houses;
private $date_placed;
private $date_picked_up;
private $head_started;
private $head_picked_up;
private $pounds_picked_up;
private $pounds_sold;
private $base_rate;
private $performance_rate;
private $fuel_rate;
private $other_rate;
private $feed_pounds_consumed;
public function __construct() {}
//Then a bunch of getters and setters.
}
My goal is to use the same object for both creating and retrieving a flock. However, in my database, I have created several "master" views--views that join together all lookup tables and perform any basic calculations on the data that can be performed at the row level. In this example, in my master view for flocks, I have joined the integrators table with my flocks giving me an integrator_name column in my view. The same goes for the members and farms tables. I have also found the difference in days between date_placed and date_picked_up giving me a column called total_time_in_facility. In my current version of this application, all this data is simply stored in an array and accessed using magic methods (__get). So if I wanted to get the integrator name, I would simply use $flock->integrator_name. However, if I were to use my Flock class to populate a database, I would not pass the integrator name--I would simply pass the values I've listed above.
My question is--what is the best way to get this extended data about a flock? Should I use another class to handle this (like FlockDetails)? Should I not be using this master view at all? Instead of performing any calculations in my view, should I simply be performing those within the class itself? I was under the impression that I should only be using a single class to describe a flock. If I were to create a new flock, I would use this class. If I wanted to retrieve on, I would simply populate it using a factory. But if I populate it using a factory, what about the additional details contained in my master view? Can anyone help me clear this up? Thanks!
Well, I would probably have a class Flock which represents a single row in the table "flocks" and class FlockEx which extends the class Flock by adding the fields you get as a result of the calculation in db. So, when you want to update or add a new flock to the db you use Flock class and when you retrieve flock fields along with the calculated ones you use FlockEx. Makes sense?
In my database I've a table file and a table file_content. The file table stores the metadata of the file such as name, mime and some more. The file_content stores a blob with the file content. I'm not storing the blob in the same table as the metadata for performance reasons only.
For a 'version 2' of my project I'm looking into Doctrine (2.3). To me, a "File" seems to be one entity, with properties such as name, mime, extension, content that should be used like this:
$file = new File();
$file->setName('hello.txt');
$file->setMime('text/plain');
$file->setContent('Hello world!')
$em->persist($file);
$em->flush();
Is this behaviour possible? To me it makes no sense to create two entities for something that's really just one entity. I could not find anything about it in the documentation and I read in a 2-year-old topic that it isn't possible in Doctrine 2.1: Doctrine 2.1 - Map entity to multiple tables
Someone any suggestions how to handle this correctly? I'm new to Doctrine and have been playing around with it a bit to see if it's the right choice for my project. Thanks.
I'm going to suggest a different approach even though it's a somewhat older question. Instead of making two entities, you should make three.
Create FileMetadata, which holds name, mimetype, etc. Create FileContent, which holds the content. Then, create a third entity File which holds a one-to-one connection with one of each of the other types, and give it a bunch of methods that simply call the matching methods on the sub entities.
So for example in File, the setName method would look like this:
public function setName() {
$this->getFileMetadata()->getName();
}
And set would look like this:
public function setName( $name ) {
$this->getFileMetadata()->setName( $name );
}
The constructor of the File entity should create a new FileMetadata and a new FileContent on creation and should NOT have a setFilemetadata or setFilecontent method; these two entities should be completely shielded from your application.
Now you have the File entity, which handles exactly like you want (a single entity, no additional sub-entities) which is still stored neatly in two (well, three really) different tables.
Do you have the ability to alter the schema of your database? If so, I'd consider consolidating this into one table.
Barring that, you may want to try a one-to-one relationship in Doctrine. Perhaps something like:
class File {
private $id;
private $name;
private $mime;
private $content;
}
class Content {
private $id;
private $data;
private $fileId;
}
If you map Content->fileId with a one-to-one relationship to File->id, then you can do things like:
$file->getContent()->getData();
$file->getContent()->setData("something different");
Here's some more info on one-to-one mappings: http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#one-to-one-unidirectional
You could map both tables, resulting in File and FileContent, and add a one-to-one relationship between the two.
Then add getter/setter methods for the file content in the File class. In these you'd have to call the corresponding methods in FileContent.
I'd like to use a different table prefix for my session database. So, in my config file I have my table prefix set as "pre1_", but I'd like my sessions to use a table with the prefix "pre2_" -- is this possible?
Thanks.
I remarked the line in my model so it would not be executed;
// public $tablePrefix = 'cc_';
Then, in my AppController.php beforeFilter I added
$this->modelname->tablePrefix=$this->Auth->user('company_prefix').'_';
In my users table of my authorization I have a field called company_prefix which is prepended to certain tables that are unique to that user.
I would love to see more on this subject.
I get a lot of 'Indirect modification of overloaded property DifferentController::$Modelname has no effect'
So I am looking for a way of doing this only when the model is added or the default of a controller. I don't want a beforeFilter() for every controller, so I think I will have to do some sort of isset()
For a 'company' prefix in Cakephp 2.1 - public $tablePrefix in the model and setting the tablePrefix in the controller, here is what I am doing and it is too many lines of code (one line of code per table that has a prefix per user) but it is all I can do right now.
Modela.php:
public $tablePrefix = 'anything_';
Modelb.php:
public $tablePrefix = 'anything_';
Modelc.php:
public $tablePrefix = 'anything_';
then in the public function beforeFilter() of the AppController
if (isset($this->Modela->tablePrefix)) {$this->Modela->tablePrefix = $this->Auth->user('company_code').'_'; }
if (isset($this->Modelb->tablePrefix)) {$this->Modelb->tablePrefix = $this->Auth->user('company_code').'_'; }
if (isset($this->Modelc->tablePrefix)) {$this->Modelc->tablePrefix = $this->Auth->user('company_code').'_'; }
I don't get the 'overloaded property' error this way, but in my schema, all my users have a valid company code. I also have for example the 'users' table is shared for all users (although only admins can get to a user record other than the logged in user)
However, I find this rather cumbersome, and am interested in a means that would not require every single model that is to be segregated by the model's $tablePrefix company_code setting to be defined as a line of code in the AppController. The point at which I really want to set the tablePrefix for a model is the instant I reference
$this->loadModel("Modelb");
But I spent 45 minutes playing with modelb.php in the Model directory and was not able to do much with the $tablePrefix property. When a model is instantiated or referenced, I should know, for those tables that need a $tablePrefix property, the required prefix, but I was not able to set the property dynamically at that point. (a dynamic static property? sounds like the wrong approach)
So, even though this works, I don't like it. It feels like I am using Fortran methods in a Lisp program.
within ModelasController.php:
$this->loadModel("Modelb");
$this->Modelb->tablePrefix = $this->Auth->user('company_code');
is the proper way to use a second company coded model, and I don't need the beforeFind logic.
So only the AppController has a line for every possible model in the beforeFilter to set the tablePrefix, just like above, using the php function isset to detect the model.
But when I pull a secondary model into a controller with the loadModel, I immediately follow that now by setting the tablePrefix to the company code, which I can get to in the controller. This reaches into the model object and overrides the tablePrefix static property.
I ended up changing the prefix in the database.php config file to the prefix I wanted my sessions table to have, then in each model set the $table_prefix property to the prefix those required. Seems a little weird, but it got the job done.
I read this post after doing a search for related posts.
I have a slightly different, but related problem.
Is there a way WITHOUT EVAL() (because this is a bad idea - open for abuse if someone allows a user to supply the value that is used in eval, etc) such that you can define the structure of the class, for example:
if(!class_exists($className) && dao::tableExists($className)) {
class $className extends daoObject {
public function __construct($uid) {
parent::__construct($uid);
}
}
dao::generateClass($className);
}
The reason for this is because when new core tables are added to a framework, they could be used with a generic data access object for accessing the corresponding fields (getters/setters via __call in the parent, add/insert and update/delete) without writing a class for each, without requiring the coder to write a class and then having to inspect it or writing custom code generators for the various types of tables. the daoObject does that all for me.
The intention is to use this kind of method to define a class if it doesn't exist, then write the class definition to a file.
If the corresponding table name doesn't exist, it will fail. If the class exists (e.g. the next time it is run) then it won't define it. If it doesn't exist but is a tablename, you could create it, use it and save it the first time you call it, which would occur when new tables are inserted and a script is run to insert data. The authors will define only the table fields and sample data via csv. This script will generate classes and import the data in one hit. I COULD write the definition to a file, then include it, which seems like it could work, but I want to do that AFTER I've modified the properties of the object so I don't have to write to files twice to make it work.
This is simplified, but is it possible?
I don't think it's possible; as you said, the best option is probably to write the class to a file, then autoload that/those classes.
You can't use a variable for a class name (unless as you say, with eval()).
So if you really need to create DAO objects at runtime for tables for which no class is defined, perhaps you should make a DAO class for "other table" and pass the name of the table in the constructor.
class OtherTable extends daoObject {
public function __construct($uid, $tableName) {
$this->table = $tableName;
parent::__construct($uid);
}
}
$FootballTable = new OtherTable($uid, 'football');
trigger_error("You need a new table class!", E_USER_WARNING);
If your logs show that you have any of these user-warnings, you should use that as a reminder to go create a proper class for the new table(s).
Re your comment:
Generating code for a new class at runtime, even as a fallback condition, is not a good habit. The risk is that some untrusted content sneaks into your class definition (such as user input, but it can be something else). Then you have a Code Injection security problem.
Either you need a generic any-table DAO class like I showed, or else the best solution is that you create new DAO classes during development, at the time you create new tables in your database. Why is that not your solution?