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.
Related
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"
After debugging quite a bit, I noticed a really strange behaviour inside of CakePHP's (2.x) Model usage:
When I changed the Model ID and used read(), on a completly different object instance with a relation to the same Model, it overwrites the old Model data.
// set the user, by using the 'User' model
$this->User->id = 1;
$this->User->read();
print_r($this->User->data); // works correctly
$instance = new Notification(); // this has a relation to the 'User' model
print_r($instance->User->data); // == $this->User->data! why?!
$instance->User->id = 2;
$instance->User->read();
print_r($this->User->data); // == $instance->User->data!
Why are those Models connected with each other? Shouldnt they be completly separated, since it's a new instance? I mean, I'm setting the 'User' model for the Notification, not for $this
And if that's default behaviour - how can I read() data into different instances, whitout changing other models? Do I really need to manually create a new 'User' instance and store it somewhere in $instance to avoid this behaviour? That sounds rather ugly to me.
Model instances are singletons
The following two objects in the question are identical:
$this->User
$instance->User
Because they are literally the same object, the path used to access an object doesn't modify the behavior of the (User) object itself.
That's simply how ClassRegistry::init works - it stores a reference to model instances - and will return the same object when queried for the same alias (className) again.
Don't create models using new
Doing that is not normal - and will likely cause problems or at least confusion in the future. To get a reference to the Notification model, use $uses, loadModel or ClassRegistry::init as appropriate.
Don't use Model::read
Do I really need to manually create a new 'User' instance
Absolutely not, that's not how models are intended to work with CakePHP. A model class is effectively the interface to the database, it's not a representation of a single row (except when calling save).
The simplest way to avoid a significant number of problems is to not use Model::read at all, and instead use any appropriate find call; A more complete code example would permit a more specific answer.
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?
I have to make a view that shows unrelated data from multiple tables. I am new to cakePHP (and PHP in general) and as far as my understanding goes, each model is a depiction of just one table. I know that we can define associations with other tables, but in my case I need to give access to data that is no way related to the model who's view will be opened.
Example:
Say there is a blogging platform and we are currently viewing a post. (Model - Post, function - Read). Now I want a list of (Say) subscribers of our newsletter. This data is not related to the model and hence, I don't think the data will be accessible to the controller. Please tell me how to get this data in view directly.
In your controller, when defining the class, add a class attribute $uses to tell your controller which models to load.
class SomeController extends AppController {
public $name = 'Some';
public $uses = array( 'Model1', 'Model2' );}
And then, in your method, you just call that model:
$result = $this->Model1->find('all');
$result2= $this->Model2->find('all');
There are a few ways to do this. Here is gwoo's synopsis of how they differ and when to use which:
App::import() only includes the file.
So you would new to create a new
instance every time. This is not
recommended
ClassRegistry::init() loads the file,
adds the instance to the a object map
and returns the instance. This is an
easy and convenient way to access
models.
Controller::loadModel(); Uses
ClassRegistry::init() adds the model
to a property of the controller and
also allows persistModel to be
enabled.
While you "can" do any of these
things, you should ask yourself why
you are creating dependencies on
models that are not natural to the
controller. If you "have" to do use
any of these, then I would do so in
reverse order of the way i described
them. IE, Controller::loadModel ()
then CR::init() and actually I never
use App::import() for models. Hope
this helps.
See this page for the full discussion: http://groups.google.com/group/cake-php/browse_thread/thread/137c57b4eb010317
In addition, some other answers have suggested including the unrelated model in the $uses array, but I would avoid this method as it is really intended to tell the model which database table to use and implies that its members are central to the purpose of the model, which is not the case in the situation you describe.
If you're going to use it in a couple of functions in the same model, you should specify it inside the Controller via:
$uses = array('Post', 'Suscriber');
Now, if it is a elemnt of the layout, you should set it on an element.
In the view:
$this->renderElement('suscribers-list');
Now you must create a suscribers-list.ctp file in views/elements. From there import the model:
App::Import('Model', 'Suscriber');
$this->Suscriber = new Suscriber();
$suscribers = $this->Suscriber->find('all');
pr($suscribers);
It's not pretty, but it's what works for me. I don't know if there's another way.
You can do this with less code, and slightly simpler. You can make use of the AppController::loadModel('ModelName'). (reference) This method takes care of initilization step that metrobalderas suggested so then if becomes
$this->loadModel('Subscriber');
$subscribers = $this->Subscriber->find('all', ... );
There was a function that was deprecated in 1.2 that was just loadModel('ModelName'); but not the AppController method. Also it should be noted that you should not directly load models in an element, as that is not really in the spirit of MVC. Keep that in the model. Using this method rather the var $uses = array('ModelName'); does reduce the overhead of the unrelated models in methods that don't need it, as well as reduces some of the complications that can occur when using that approach.
I would like to know where is the best place to set my db object with my model.
Should I hard coded it since my model should be designed for one project, so i set it inside my constructor or wherever i do initialization ?
or
Should I pass my db object to my constructor when instancing my object ?
What is the best way, i mean from experimented users, and efficient that'll give me more confort to use ?
Couple of things:
Most PHP projects that utilize a database connection represent that database using a Singleton pattern, if you aren't sure what this is, read up on it.
Typically I define my database connections in a configuration file which can easily be changed between environments (development, stage, production).
I'll then instantiate my database connection in a bootstrap file using the aforementioned Singleton pattern and configuration file.
My models will typically completely abstract the database/table data store, for each model I'll do something like this:
bootstrap.php
$config = load_config_data(ENVIRONMENT);
Db::setDefaultAdapter($config['database']);
Model/Table/User.php
class Table_User extends Db_Table
{
// Table name
protected $_name = 'user';
/* Do a bunch of database specific stuff */
}
Model/User.php
class User extends Model
{
public function updateUsername($userid, $username)
{
// Uses default adapter, Singleton pattern!
$table = Db::loadTable('user');
$table->update(
array('username'=>$username),
Db::quoteInto('userid = ?', $userid)
);
}
}
This is pretty much an introduction to the Model in the Zend Framework MVC, I would check it out for some ideas on how to organize your code (or save yourself some trouble and actually use the framework.)
For testability, you should pass it into the constructor rather than hard coding it. This helps you to write unit test because you can mock your DB object.
I would not hard code it, even if the code is never used for another project simply moving from a test database to a live database may require locating and changing the code in the model class. That would be far better placed in some kind of configuration file.
Personally, I would have the db object defined in whatever you use as a bootstrap - and then have the model(s) use that single object.