Database agnostic virtual fields in CakePHP - cakephp

In CakePHP 1.3 there is a feature for virtual fields but it's coupled with the database that you are using. For example:
var $virtualFields = array(
'full_name' => 'CONCAT(User.first_name, " ", User.last_name)'
);
This would work for MySQL but not for MS SqlServer. Is there a way to make this database agnostic?
I'm still in the middle of developing an application and still not sure what database we'll be using in production. That's why I want to keep all the database access as agnostic as possible.

You could dimension your Model::virtualFields property such that it had rules for each database:
var $virtualFields = array(
'mysql' => array(
'display_name' => 'CONCAT(User.name, " (", User.team, ")")',
),
'postgres' => array(
'display_name' => 'PgConcatStuff(...)',
),
'mssql' => array(
'display_name' => 'MsConcatStuff(...)',
),
);
The trick then is to catch the above property and manipulate it on-the-fly so Cake never realises:
class AppModel extends Model {
function beforeFind($queryData) {
$ds = $this->getDataSource();
$db = $ds->config['driver'];
$this->virtualFields = $this->virtualFields[$db];
return parent::beforeFind($queryData);
}
The above code was tested with a simple find on a single model. More testing may be needed to check for edge-cases involving related models. The finished functionality belongs in a behavior. :)

Related

Existing database integration with Laravel 5.0

I am new to laravel. I've got the whole database designed in phpmyadmin. Now I want to integrate the existing database in Laravel. Is there any way to do that? If so then do I have to create models?
Yes, you can use your database in laravel but at first you have to provide your database credentials to allow the framework/Laravel to access your database. So, you can use a .env file or simply you can use the config/database.php to provide credentials, for example, to use your mySql database all you need to setup the database configuration as follows:
'default' => env('DB_CONNECTION', 'mysql'),
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'your_database_name'),
'username' => env('DB_USERNAME', 'your_database_username'),
'password' => env('DB_PASSWORD', 'your_database_password'),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
'engine' => null,
]
Then regarding your second question, yes, you have to create models for each table or you can use DB facade to run queries directly from your controllers (not recommended). For example, to access your posts table you can create a Post model in app folder which may look something like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
// If your table name is other than posts
// then use this property to tell Laravel
// the table name.
protected $table = 'posts';
}
Then you can use something like this:
$posts = \App\Post::all(); // Get all posts
$post = \App\Post::find(1); // Get the post with id 1
If you don't want to use Eloquent Model like one I've created above then you may use something like this:
$posts = \DB::table('posts')->get(); // Get allp posts
These are some basic examples to get you start. To learn more about the use cases of models/database you should visit Laravel website and read the manual (find Database section and check Query Builder and Eloquent).

CakePHP 3: search form with get method and find condition, how to prevent SQL injection?

I have an HTML form with the GET method, and five text input field, which should help to filter the data. When users fill one or more fields, these data are shown as url query.
My question is how to safely use the this query data without the possibility of SQL injection?
EDIT
Of course, is a simple filtering of user data, by name, location, etc., not fulltext search.
'first_name LIKE' => '%'.$this->request->query('first_name').'%'
Where is in the documentation explained bind method, like ?
->bind(':name', $this->request->query('name'))
To avoid SQL injection vulnerabilities, you can use query placeholders.
Your code should look something similar to
$query = $this->Users->find()
->where([
'first_name LIKE' => '%:name%'
])
->bind(':name', $this->request->query('first_name'));
More information in:
Binding Values in Cookbook 3.x: Database Access & ORM
Query::bind()
You should consider using Search Plugin
Its just very simple, write this in controller
public $components = array(
'Search.Prg'
);
public function index() {
$this->Prg->commonProcess();
$this->set('users', $this->paginate($this->Users->find('searchable',
$this->Prg->parsedParams())));
}
And this one in Model
public $filterArgs = array(
'first_name' => array(
'type' => 'like',
'field' => 'first_name'
)
);
public function initialize(array $config = []) {
$this->addBehavior('Search.Searchable');
}
and you are done.
For more examples, visit here

Unable to specify Model->alias

I'm using a custom Class in my program which can connect to multiple tables as and when I need to.
I'm trying to create a new instance of the class, and I can pass in a different table name and alias via the constructor. This works for the table name, but it doesn't set the Model alias as it should in the documentation
Creating an new Object. The Class being instantiated is Lists
$lists = new Lists(null, 'list_musicians', null, null, 'Musician');
Class Constructor
public function __construct($id = false, $table = null, $ds = null, $name = null, $alias) {
parent::__construct($id, $table, $ds, $name, $alias);
$this->virtualFields['full_name'] = sprintf(
'CONCAT(%s.first_name, " ", %s.last_name)', $this->alias, $this->alias
);
//debug($this->alias);die;
}
This will connect to the table named 'list_musicians' or whatever table name I pass in, but the $alias field does not get assigned to $this->alias
If I reassign the alias manually, the virtual fields are not included in the same array as the result, as specified in the documentation. The alias is always Lists
How can I set the model alias via the constructor?
I'm using a custom Class in my program which can connect to multiple
tables as and when I need to.
Honestly that's a pretty bad idea in the most cases. If you already read the MVC chapter from of the book read it again, if not read it now.
And you're wrong, the constructor of the model class doesn't know a 4th argument for the alias, check your own link and read it again.
public function __construct($id = false, $table = null, $ds = null)
However, if you want to insist on bad practice you can always instantiate Model objects by using ClassRegistry::init().
Examples Simple Use: Get a Post model instance
ClassRegistry::init('Post');
Expanded: array('class' => 'ClassName', 'alias' =>
'AliasNameStoredInTheRegistry');
Model Classes can accept optional array('id' => $id, 'table' =>
$table, 'ds' => $ds, 'alias' => $alias);
Just use Model as class name and provide whatever table you want.
Solution to this is just to create model associations directly to the db tables. Use the table name as the classname and voila
'Religion' => array(
'className' => 'list_religions',
'foreignKey' => 'religion_id',
'conditions' => '',
'fields' => '',
'order' => ''
)

cakePHP virtualField within condition of another model

I have 2 models, User and Entity. I need to on my entities page, have pagination, and a simple search function. The search function will filter the entities, but the data it filters is a virtualField within the User model.
The Entity model:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
};
The virtual field in the User model:
public $virtualFields = array("searchFor" => "CONCAT(User.first_name, ' ',User.last_name, ' ',User.email)");
And the condition within the entity controller:
$conditions["User.searchFor LIKE"] = "%".str_replace(" ","%",$this->passedArgs["searchFor"])."%";
$this->paginate = array(
'conditions' => $conditions,
'order' => array('Re.created' => 'DESC'),
'limit' => 20
);
From what I can read this is not possible because I cannot use virtualFields within a query of an associative model, and it says I must use it in "run time", but I really do not understand how to do that, or how to do that in this instance. Any ideas or a way I can get this to work?
You could try attaching the virtualField to the Entity model like so
$this->Entity->virtualFields['searchFor'] = $this->Entity->User->virtualFields['searchFor'];
But you have to make sure that a join is done and not 2 queries.
I believe its discussed in the book.
Edit: Book page
For this purpose I needed to do a search on a concatinated string from an associated model. Normally this can be done using Virtualfields in cake, but cake does not support using a virtualField from an associated model in the search.
Seeing that the 2 models were already linked with the belongsTo, I merely changed the condition to:
"conditions" => "CONCAT(User.first_name, ' ',User.last_name, ' ',User.email) LIKE" => "%".str_replace(" ","%",$this->passedArgs["searchFor"])."%"
Probably not the most elegant solution, but it works.

CakePHP bindModel HABTM Save

I want to create a binding from one model to my User model on the fly so that the JOIN is not called every time that I perform a find on that model. I am using the binding to perform a HABTM save. However, when I use the bindModel function, the HABTM data is not saved in the database.
What makes this odd is that if I move my binding to the User model, then the save works perfectly. I don't see any indication in the documentation that the save behavior would be different when the association is made in the model versus the bindModel function (though, I may have missed it if there is any).
Here is my bindModel code in my controller:
$this->User->bindModel(
array(
'hasAndBelongsToMany' => array(
'Othermodel' => array(
'className' => 'Othermodel',
'joinTable' => 'othermodels_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'othermodel_id',
'unique' => true,
)
)
)
);
if($res = $this->User->save($data)){
return true;
}
And this is my user model.
class User extends AppModel {
public $name = 'User';
public $belongsTo = array();
public $hasOne = array();
public $hasMany = array();
public $hasAndBelongsToMany = array(
'Othermodel' => array(
'className' => 'Othermodel',
'joinTable' => 'othermodels_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'othermodel_id',
'unique' => true
)
);
Again, I only have the relationship active in one place at one time, so I know the problem is not with the binding itself. It seems the problem is solely related to the fact that I have tried to use bindModel. Is this the intended behavior?
Based on the responses in this article, it looks like the bindModel function only exists for finds.
CakePHP: Bind Model not working
Though there's no information in the CakePHP 1.3 documentation that states that the bind is only for finds, though that would explain the behavior...
http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html
So, I am marking this as answered.
I'm not sure if this is relevant, but are you sure you are not doing any other find operations between the binding and the save. bindModel only binds the model for the next find operation so you may need to pass true through to bindModel to tell it to keep the binding around.
Relevant links: CakePHP: Bind Model not working and http://groups.google.com/group/cake-php/browse_thread/thread/316c9796603eac57?pli=1.
I hope this helps.
Try this,
$this->Message->bindModel(
array(
'belongsTo'=>array(
'User'=>array(
'foreignKey'=>false,
'conditions'=>array('Npl.to=User.id '),
'fields'=>array('recive')
),
'User_e'=>array(
'className'=>'User',
'foreignKey'=>false,
'alias'=>'User_e',
'conditions'=>array('Npl.from=User_e.id '),
'fields'=>array('recive')
)
)
)
);

Resources