Phalcon execute sql query in the model - php

Currently I decided to look at Phalcon php as an alternate php framework to Codeigniter. I followed the tutorials on the website and it is pretty sweet the way it works. I'm still trying to wrap my head around few things though.
From my understanding, Models are bind with Database and mapped to the table in the database. I have a project, where I need to use 2 or more databases. The project has a backend (one database) and multiple frontends (another database). The easiest way is to run custom MySQL queries to fetch data from multiple databases. I'm not sure how to do that from the Model in the Phalcon. I looked over stackoverflow, tried few suggestions, but still no luck.
I would guess there should be some easy way to do so from the Model like $result=$this->query("SELECT * FROM backend.users")->fetch(); but it doesn't work.
Here what I have:
Controller:
class SignupController extends \Phalcon\Mvc\Controller{
function indexAction()
{
}
function registerAction()
{
$user = new Users();
$result=$user->saveNewUser();
print_r($result); // Nothing
//$result=$this->db->query("SELECT * FROM phalcon.system_users")->fetchAll();
//print_r($result); // Works
$this->view->disable();
}
}
Model:
class Users extends Phalcon\Mvc\Model
{
public function saveNewUser()
{
return $this->db; // how to run the query???
}
}
Bootstrap:
try {
//Register an autoloader
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
'../app/controllers/',
'../app/models/'
))->register();
//Create a DI
$di = new Phalcon\DI\FactoryDefault();
//Setup the database service
$di->set('db', function(){
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "123456",
"dbname" => ""
));
});
//Setup the view component
$di->set('view', function(){
$view = new \Phalcon\Mvc\View();
$view->setViewsDir('../app/views/');
return $view;
});
//Setup a base URI so that all generated URIs include the "tutorial" folder
$di->set('url', function(){
$url = new \Phalcon\Mvc\Url();
$url->setBaseUri('/phalcon/');
return $url;
});
//Handle the request
$application = new \Phalcon\Mvc\Application($di);
echo $application->handle()->getContent();
} catch(\Phalcon\Exception $e) {
echo "PhalconException: ", $e->getMessage();
}
I liked the way Codeigniter had it, not sure if Phalcon have a simple way of doing that. May be I need to load the extension or library to do that in the Model.
Thanks in advance!

Thanks jodator,
But it is a little bit different that I needed. I wanted to execute sql queries from the Model.
After spending more time testing and experimenting I think I figured it out. Just in case if someone has the same need to be able execute mysql queries from the model, here the way I figured it out. I'm not sure if impacts the performance, but it works.
Create a new Model class and call it for example BaseModel.php with the next inside:
class BaseModel extends Phalcon\Mvc\Model
{
public $db;
public function initialize()
{
$this->db=$this->getDi()->getShared('db');
}
}
The BaseModel will extend the Phalcon Model and I created a public property called $db. Then on the initialize() I used $this->getDi()->getShared('db') to grab shared dependency injector and assigned it to our $this->db. Now all Models that extend this BaseModel will have access to that property. Then in my Users Model I have next:
class Users extends BaseModel // Users extends out BaseModel and will have access to $db
{
public function test()
{
//print_r(\Phalcon\Di::getDefault()->getShared('db')); // This is the ugly way to grab the connection.
$result=$this->db->query("SELECT * FROM phalcon.system_users"); // Working now
echo $result->numRows();
print_r($result->fetchAll());
}
}
Now it works beautifully. I also figured out one more thing that might be interesting to someone who wants to use mysql queries (PDO) in Phalcon. I always use FETCH_ASSOC when fetching data, and to make life easier you can set up FETCH_ASSOC by default at the connection this way you don't need to setAttribute every time fetching data. Here is how I have done it. At the bootstrap, when setting DI class for the database connection, you can include the options....
//Setup the database service
$di->set('db', function(){
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "123456",
"dbname" => "",
'options' => [PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_PERSISTENT => TRUE,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC],
));
});
As you see the last option sets PDO::ATTR_DEFAULT_FETCH_MODE.
If anyone has better way to do that, please post it here.
I hope it will help newbies to Phalcon like me :)

You need to setup two database services in you config, like:
//Setup the database service
$di->set('db', function(){ /* like in what you have */ });
// then the other one
$di->set('dbBackend', function(){ /* like in what you have */ });
Then in your models change the db service
public function initialize()
{
parent::initialize();
$this->setConnectionService('dbBackend');
// or $this->setWriteConnectionService('dbB') and $this->setReadConnectionService('dbA')
}
Then just $model->create()
But if you want to run a query on different databases you can look at Pdo\Mysql Adapter.
Also models can have set table name $this->setSource() and schema $this->setSchema().

Related

PHP Phalcon 3.4 - Can I specify connection per query , using Phalcons/Model query methods?

sorry if it'll be a bit messy (English is not my native tongue so excuse me for anything not clear enough!)
I'm Using Phalcon 3.4, with PHP 7.3.16
Let's say I have got a basic setup of
class A extends Model {...}
class AController extends Controller {...}
I've set up 2 separate connections to the DB in the DI
// adapter using read / write connection
$di->set('db', function() {
return new ...
});
// adapter using read only connection
$di->set('db_reader', function() {
return new ...
});
db service acts as the default connections when querying using the Models (::find(), ::query(), ->save())
the question is, can I force a specific connection to a specific query, from the controller?
I know I can
class A extends Model {
public function initialize() {
$this->setReadConnectionService('db_reader');
$this->setWriteConnectionService('db');
}
}
but I want specific read operations happening in the controller, to use the db_reader connection, and the rest can still be queried using db which has the read/write permissions.
something like
class AController extends Controller {
public function AAction() {
$a = A::query()->setReadConnection('db_reader')->Where('....')....;
}
}
is it possible?
Thanks ahead and sorry for the trouble of reading so far :)
Well I think I would just use the query builder like
$sql = 'SELECT * FROM robots WHERE id > 0';
// Base model
$robot = new Robots();
// Execute the query
return new Resultset(
null,
$robot,
$robot->getReadConnection()->query($sql)
);
Reference: https://docs.phalcon.io/3.4/en/db-phql#creating-queries-using-the-query-builder
With the assumption I understand what you are asking is specifying connection based on query. This also assumes you have separate functions for each query. So you could do something such as get_class_methods() to get the actions available and compare that to the current one it is executing. Based on that you can set the connection.
...
use Phalcon\Mvc\Dispatcher;
{
public $functions;
public $read_functions;
public function initialize(Dispatcher $dispatcher)
{
$action = $dispatcher->getActionName();
//define this however you want -- several ways to define this
$this->read_functions = array('some_function');
$this->functions = get_class_methods('someController');
if(in_array($action, $this->read_functions)){
$this->setConnectionService('db_reader');
}else{
$this->setConnectionService('db');
}
}
public function some_function()
{
//I only read things using db_reader
}
public function some_function()
{
//I use db
}
You can expand this to use a switch and use case statements to do the logic.

What is a good way of associating a Model instance with it's own Class in Laravel?

I'm sure there is a common pattern for this kind of thing, and I'm struggling with search terms to find answers, so please bear with me if is this a dupe.
I have a few Classes in my app that create pretty standard Models that are stored in a relational database, eg;
// AtsType::name examples = 'XML', 'RSS', 'SOAP'
class AtsType extends Model
{
public function ats_instances()
{
return $this->hasMany('App\AtsInstance');
}
public function import()
{
}
}
What I need that import() method to do, however, somehow invokes a class/interface/contract/whatever based upon the actual model instance. So something like this;
AtsTypeRss::import()
AtsTypeXml::import()
AtsTypeSoap::import()
I'd like them to be standalone classes, in order to eventually use some artisan commands that will generate them for a developer, along with a data migration to create the new model names into the database.
I'm just unsure how to go about this.
You could try something like (as seen here), I've searched how to use variable in namespace :
class AtsType extends Model
{
protected $import_method = 'MyMethod';
public function ats_instances()
{
return $this->hasMany('App\AtsInstance');
}
public function import()
{
$string = $this->import_method;
$class = '\\controller\\' . $string;
$newObject = new $class();
}
}

How to trigger Eloquent Manager in Slim 3.1 Dependency Injection

I have write my code to instantiate Eloquent Capsule/Manager using slim DI like this
$container['db'] = function ($c) {
$settings = $c->get('database');
$db = new \Illuminate\Database\Capsule\Manager;
$db->addConnection($settings);
$db->setAsGlobal();
$db->bootEloquent();
return $db;
}
And I have my route like this
$app->get('/adduser', function() {
$user = new Users;
$user->name = "Users 1";
$user->email = "user1#test.com";
$user->password = "My Passwd";
$user->save();
echo "Hello, $user->name !";
});
When I run the route in browser it will produce error in web server error log
PHP Fatal error: Call to a member function connection() on a non-object in /home/***/vendor/illuminate/database/Eloquent/Model.php on line 3335
In my opinion this is happened because the Eloquent Capsule/Manager is not triggered to be instantiate by DI.
I found a solution to solve this by declare the Model with custom constructor like this
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Capsule\Manager as Capsule;
class Users extends Eloquent {
protected $table = 'users';
protected $hidden = array('password');
public function __construct(Capsule $capsule, array $attributes = array())
{
parent::__construct($attributes);
}
}
But I don't think this is a clean solutions, because I have to rewrite all my Models using custom constructor.
I need help to find solutions for my problem.
I try to use code below:
$app->get('/adduser', function() use ($some_variable) {
// create user script
});
but so far I don't know how to trigger $container['db'] using this method. I really appreciate a help here.
It's probably not a good idea to inject your capsule manager into each model.. As you say yourself, that's going to be a pain to manage.
Have you tried this code outside of the closure? ie. in the bootstrap part of your app..
$db = new \Illuminate\Database\Capsule\Manager;
$db->addConnection($settings);
$db->setAsGlobal();
$db->bootEloquent();
The setAsGlobal function makes the Capsule Manager instance static, so the models can access it globally.
Just to note, convention is to name your model classes in singular form. ie. User rather than Users.

Cannot get DataMapper to work in CodeIgniter

I'm trying to implement an ORM in a CodeIgniter application, but cannot get it to work. To start I'm just trying to instantiate a simple test model:
<?php
class Cart extends DataMapper
{
public function __construct()
{
// model constructor
parent::__construct();
}
var $validation = array(
'username' => array(
'label' => 'UserName',
'rules' => array('required', 'trim', 'unique', 'alpha_dash', 'min_length' => 1, 'max_length' => 50),
)
);
}
?>
And then in the Controller I try this:
public function __construct()
{
parent::__construct();
$this->load->model('cart');
}
public function index()
{
$cart = new Cart();
}
But I don't even get past the constructor. The debugger stops and gives me a message saying "Waiting for an incoming connection with ide key xxxxx" (random number)
BTW the cart model class file name is in lower case, but the class name in upper case. I tried both in the constructor.
I have followed the instructions for installation carefully, copying the two datamapper files to libraries and config folders, as well as autoloading the datamapper library.
But it just doesn't work. Am I missing something? The table I'm trying to map is only a test table that actually only has an id and a username field. I don't actually understand the validation array, but just followed the examples in the docs and modified to my field. The id field doesn't seem like anyone has put in the validation array.
I should also mention that I'm a newbie at CodeIgniter.
Your code seems mostly correct for use with DataMapper ORM and CodeIgniter.
To explain things a bit, DataMapper is just an abstraction layer. It handles a lot of the necessities when working with databases and mapping your objects to your tables. That being said, you don't have to load your models, etc. As long as you are autoloading your database library and datamapper library, you can use DataMapper.
The validation array lets DataMapper know the requirements to your properties. So, if you try to save an object and one of the properties that you've created/changed doesn't meet those requirements, then your save will fail and you'll get an error message:
// For example
if ($myObj->save())
{
// $myObj validation passed and is saved to db
}
else
{
// $myObj validation failed, save did not complete
echo $myObj->error->string;
}
Codeigniter already has a library named Cart, so you wouldn't want to name your model Cart. So you could rename that model to Basket or something else that makes sense.
I know you're still just trying to get things to work, but I feel you need to think about your data structure a bit. You wouldn't save the username in the Cart object, that's why we use relations. So, I would structure it a bit like this:
// baskets table (a table represents many baskets, therefore it is plural)
id
user_id
blah
blah
created
updated
// users table
id
username
email_address
created
updated
// basket model (a model represents 1 basket, therefore it is singular)
class Basket extends DataMapper
{
public function __construct()
{
parent::__construct();
}
var $has_one = array('user'); // each basket belongs to one user
var $validation = array(...);
}
// user model
class User extends DataMapper
{
public function __construct()
{
parent::__construct();
}
var $has_many = array('basket'); // each user can have many baskets
var $validation = array(...);
}
// controller
public function __construct()
{
parent::__construct();
}
public function index()
{
$basket = new Basket();
$basket->blah = 'whatever';
$basket->save();
// at this point, $basket is saved to the database
// now let's add it to the user
$user = new User();
$user->where('id', 1)->get(1);
// now we have a user
// save the relationship to the basket
$user->save($basket);
// now $basket->user_id == 1
// get the username from the basket
$u = $basket->user->get();
$username = $u->username;
// yes, there are faster and shorter ways to write most of this,
// but I think for beginners, this syntax is easier to understand
}
The CodeIgniter documentation about models states that you can load a model by calling
$this->load->model('Model_name');
in the constructor, and that you can access this model in your controller by doing
$this->Model_name->function();
So you should change your Controller code into
public function __construct()
{
parent::__construct();
$this->load->model('Cart');
}
public function index()
{
$this->Cart->functionCall();
}

Zend database adapter - register in bootstrap.php?

I am trying to register my default database adapter in my bootstrap.php file so that I can access it where ever I am. This is my code so far:
//bootstrap.php
protected function _initDb()
{
$dbAdapter = Zend_Db::factory(Zend_Registry::get('configuration')
->resources->db->adapter,
Zend_Registry::get('configuration')
->resources->db->params->toArray());
Zend_Registry::set('dbAdapter', $dbAdapter);
Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);
}
I am then trying to call it in one of my models by saying:
//exampleModel.php
$select = $this->_getDbAdapter()
->select()
->from(array('t' => $this->_getTable()->getName()),
array('name'))....
However I am just getting the error:
Call to undefined method Application_Model_Example::_getdbAdapter() in...
So obviously it is looking for it within my current class and can't find it...
You need this in your Model_Example
public function _getSqlAdapter()
{
return Zend_Registry::get('dbAdapter');
}
Or directly call Zend_Db_Table::getDefaultAdapter() instead of $this->_getDbAdapter()
In the code provided you don't appear to be calling it the adapter from the registry. You would need to use Zend_Registry::get('dbAdapter');
What class does Application_Model_Example extend?
I have Zend_Db_Table::setDefaultAdapter($dbAdapter); in my bootstrap (notice its Zend_Db_Table, not Zend_Db_Table_Abstract).
Then in my models, I would just use
$select = $this->->select()
->from(array('t' => $this->_getTable()->getName()), array('name'))....
assuming your model extends Zend_Db_Table?

Categories