Using an extended Kohana DB class for multiple databases with ORM - php

On the current project I'm working on, data is spread across two databases. The method we're trying to use is to use an alias for the second database and then extending the database class to replace the alias with the actual database name.
In /classes/database/mysql.php, we've added this:
class Database_MySQL extends Kohana_Database_MySQL {
public static $alias;
public static $sprtDbName;
public function __construct($name, $config) {
$con = $config['connection'];
self::$sprtDbName = "$con[database]_support";
parent::__construct($name, $config);
}
public function query($type, $sql, $as_object = FALSE, array $params = NULL) {
$sql = str_ireplace('SUPPORT_ALIAS', self::$sprtDbName, $sql);
return parent::query($type, $sql, $as_object, $params);
}
}
And in /config/database.php, we have this:
$db_config = array(
'dev_local' => array(
'type' => 'mysql',
'connection' => array(
'hostname' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'db_name'
),
'table_prefix' => '',
'charset' => 'utf8',
'caching' => FALSE,
'profiling' => TRUE,
),
'support' => array(
'type' => 'mysql',
'connection' => array(
'hostname' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'SUPPORT_ALIAS'
),
'table_prefix' => '',
'charset' => 'utf8',
'caching' => FALSE,
'profiling' => TRUE,
),
);
Here's the problem: in one of my ORM classes, when I start the class off like this, it works fine:
class Model_Something extends ORM {
protected $_table_name = 'SUPPORT_ALIAS.something';
public $doc_id = null;
public $document_compiled = null;
But when I use this method:
class Model_Something extends ORM {
protected $_table_name = 'something';
public $doc_id = null;
public $document_compiled = null;
protected $_db = 'support';
I get this error:
Database_Exception [ 1044 ]: Access denied for user 'username'#'localhost' to database 'SUPPORT_ALIAS'
The alias never gets replaced. What am I missing?

I can't really answer your question per se, but I would use the Kohana built-in support for non-standard databases:
http://kohanaframework.org/3.2/guide/orm/models#use-a-non-default-database
So, instead of:
protected $_db = 'support';
Use:
protected $_db_group = 'support';
And thus, no need for the class Database_MySQL extends Kohana_Database_MySQL
Hope this helps.

Related

zf2 dynamic database connection (with parameters)

I'm trying to create a ZF2 application with multiple databases. Based on a user, the database should be dynamically set.
Right now I've the following:
database.local.php
return array(
'db' => array(
'adapters' => array (
'master_db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=master_db;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'USERNAME',
'password' => 'PASSWORD'
),
'tentant_db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=tenant_db;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'USERNAME',
'password' => 'PASSWORD'
),
)
),
'service_manager' => array(
'abstract_factories' => array(
'Zend\Db\Adapter\AdapterAbstractServiceFactory',
)
),
);
For test purposes I've created a form that has a method to fetch some data and put it in a select box. The code to get the database connection is shown in the code below.
MyController.php (in some module)
//... some code
public function someAction(){
$dbAdapter = $this->getServiceLocator()->get('tentant_db');
$form = new AddEolConnectorForm($dbAdapter);
$viewModel = new ViewModel(array(
'form' => $form
));
return $viewModel;
}
//... some more code
My question is, how can I dynamically set the dbname for the tentant_db adapter in my controller (or module)?
Thanks for your help.
The config merge event is one of zend newer event's I believe. It triggers when zend is mergin the config array's which is perfect for the problem you are facing since you can override some array key's dynamically.
public function onMergeConfig(ModuleEvent $e)
{
$configListener = $e->getConfigListener();
$config = $configListener->getMergedConfig(false);
// I'm actually not sure if you have the route match here otherwise you may have to
// use some other method to retrieve the url.
$match = $e->getRouteMatch();
switch ($match) {
case 'first-dependency':
$config['db']['adapter'] => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=master_db;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'USERNAME',
'password' => 'PASSWORD',
),
break;
case 'second-dependency':
$config['db']['adapter'] => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=tenant_db;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'USERNAME',
'password' => 'PASSWORD',
),
break;
// Pass the changed configuration back to the listener:
$configListener->setMergedConfig($config);
}
Based on the above answer I've created to following:
Module.php
class Module implements AutoloaderProviderInterface
{
public function init(ModuleManager $moduleManager)
{
$events = $moduleManager->getEventManager();
// Registering a listener at default priority, 1, which will trigger
// after the ConfigListener merges config.
$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, array($this, 'onMergeConfig'));
}
public function onMergeConfig(ModuleEvent $e)
{
$db = $this->getTentantDb();
$configListener = $e->getConfigListener();
$config = $configListener->getMergedConfig(false);
$config['db']['adapters']['tenant_db']['dsn'] = 'mysql:dbname='. $db .';host=localhost';
$configListener->setMergedConfig($config);
}
// Some more code
public function getTenantDb(){
$tenant_db = 'tenant_12345'
return $tenant_db;
}
}
I don't know if it is the best solution, but the above code is working. I think the next steps should be to put the code in a generic module or something so I can access it from all my modules.

Pagination in CakePHP 3.0

I have been playing with the developer preview version of CakePHP 3.0 and I'm stuck trying to get the new ORM working with pagination.
In my PostsController.php I have:
<?php
namespace App\Controller;
use App\Controller\AppController;
class PostsController extends AppController {
public $name = 'Posts';
public $uses = 'Posts';
public $components = ['Paginator'];
public $paginate = [
'fields' => ['Posts.id'],
'limit' => 1,
'order' => [
'Post.id' => 'asc'
]
];
public function index() {
$posts = $this->paginate('Posts');
$this->set('posts', $posts);
}
}
However the paging is working but the data doesn't come back. Apparently this because the data isn't directly returned in the new ORM but an object... Has anyone tried this yet? Knows how to fix the issue to get it working with the paginator?
I've been reading the Migration Guide: http://book.cakephp.org/3.0/en/appendices/orm-migration.html but don't see anything about combining it with the paginator.
Note: I can't debug $posts and show it here because it's about 2000 lines of code containing all sorts of stuff about the ORM. Here's a taster...
object(Cake\ORM\ResultSet) {
[protected] _query => object(Cake\ORM\Query) {
[protected] _table => object(Cake\ORM\Table) {
[protected] _table => 'posts'
[protected] _alias => 'Posts'
[protected] _connection => object(Cake\Database\Connection) {
[protected] _config => array(
'password' => '*****',
'login' => '*****',
'host' => '*****',
'database' => '*****',
'prefix' => '*****',
'persistent' => false,
'encoding' => 'utf8',
'name' => 'default',
'datasource' => object(Cake\Database\Driver\Mysql) {
[protected] _baseConfig => array(
'password' => '*****',
'login' => '*****',
'host' => '*****',
'database' => '*****',
'port' => '*****',
'persistent' => true,
'flags' => array(),
'encoding' => 'utf8',
'timezone' => null,
'init' => array(),
'dsn' => null
)
[protected] _config => array(
'password' => '*****',
'login' => '*****',
'host' => '*****',
'database' => '*****',
'port' => '*****',
'prefix' => '*****',
'persistent' => false,
'encoding' => 'utf8',
'name' => 'default',
'flags' => array(),
'timezone' => null,
'init' => array(),
'dsn' => null
)
[protected] _autoQuoting => false...
So as you can see it's a huge object and presumably the data is somewhere within it.
Apparently this because the data isn't directly returned in the new
ORM but an object...
It's not a bug it's a feature. ;) CakePHP3 returns a ResultSet object as you can see and entity objects for records. You'll have to work with these objects now instead of arrays.
I wounder if you really read the migration guide you've linked because it is all in there:
Cake\ORM\ResultSet - A collection of results that gives powerful tools for manipulating data in aggregate.
Cake\ORM\Entity - Represents a single row result. Makes accessing data and serializing to various formats a snap.
Further down on that page there is even more info about that. Take a look at the ResultSet API. You'll see that it implements Iterator, you can use it like an array:
Controller method:
public function index() {
$this->set('users', $this->Paginator->paginate($this->Users, [
'limit' => 5,
'conditions' => [
'Users.active' => 1
]
]));
}
There is a lot of documentation to read in the doc block of the paginate() method.
View index.ctp:
foreach ($users as $user) {
debug($user);
}
This will show you Entity objects. I'm not pasting the whole long debug output here, just a part of it.
object(Cake\ORM\Entity) {
[protected] _properties => array(
'password' => '*****',
'id' => '52892217-91ec-4e5d-a9f4-1b6cc0a8000a',
'username' => 'burzum',
'slug' => '',
// ...
To get something from the object back just do this:
echo $user->username;
The actual data is in the protected property Entity::$_properties and accessed by __get.
This will be in your controller.
public function index() {
$this->set('users', $this->paginate($this->Users));
$this->set('_serialize', ['users'])
}
This you can put in your action
Paginatore logic

Class design with params and dependencies

I designed my database and cache layer after Zend Framework 1, like this:
class Cache
{
public static function create($adapter, array $params)
{
$class_name = 'Cache_Adapter_' . $adapter;
return new $class_name($params);
}
}
abstract class Cache_Adapter
{
public function __construct(array $params)
{
}
}
class Cache_Adapter_File extends Cache_Adapter
{
// ...
}
Usage example:
// config.php
return array(
'cache' => array(
'adapter' => 'file',
'params' => array(
'path' => '/path',
),
),
);
// bootstrap.php
$dic->cache = Cache::create($config['cache']['adapter'], $config['cache']['params']);
This approach is great, because each cache adapter can have different parameters,
for example, file cache need path to directory where to store cache files.
Then I wanted to create cache adapter for storing data in database and realized that instead of
scalar parameter array, database abstraction class dependency is needed.
Currently database connections are registered in dependency injection container:
// config.php
return array(
'db1' => array(
'adapter' => 'mysql',
'params' => array(
'user' => 'root',
'connect_timeout' => 5,
),
),
'db2' => array(
'adapter' => 'sqlsrv',
'params' => array(
'db' => 'foo',
),
),
);
// bootstrap.php
$dic->db1 = Site:Db::create($config['db1']['adapter'], $config['db1']['params']);
$dic->db2 = Site:Db::create($config['db2']['adapter'], $config['db2']['params']);
So I wanted to ask how in addition to scalar configuration parameter array, zero or more specific dependencies can be passed to cache adapters and this can be done in config.php.
class Cache_Adapter_Db extends Cache_Adapter
{
// Instead of abstract Cache_Adapter::__construct(array $params)
// something like this is needed:
// public function __construct(array $params, Db_Adapter $db)
public function __construct(array $params)
{
}
}
There are 2 steps involved: first your cache adapter should call its parent class in the correct way:
class Cache_Adapter_Db extends Cache_Adapter
{
public function __construct(array $params, Db_Adapter $db)
{
parent::__construct($params);
}
}
Second: your factory class Cache should accept more parameters:
class Cache
{
public static function create($adapter, array $params, $optparams = null )
{
$class_name = 'Cache_Adapter_' . $adapter;
return new $class_name($params, $optparams);
}
}
The config php would look like this:
// config.php
return array(
'db1' => array(
'adapter' => 'mysql',
'params' => array(
'user' => 'root',
'connect_timeout' => 5,
),
),
'db2' => array(
'adapter' => 'sqlsrv',
'params' => array(
'db' => 'foo',
),
'options' => 'extraoption'
),
);
and in bootstrap.php:
$dic->db2 = Site:Db::create(
$config['db2']['adapter'],
$config['db2']['params'],
$config['db2']['options']
);

CakePHP doesn't create tests tables

I've tried to do some tests, but cakephp doesn't create de test_{tablename} tables! He is trying to use de original tables.
Database config:
var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => '127.0.0.1',
'login' => 'root',
'password' => '',
'database' => 'tests_clubpets',
'encoding' => 'utf8'
);
Fixture:
class AdminFixture extends CakeTestFixture {
var $name = 'Admin';
var $fields = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
...
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
);
var $records = array(
array(
'id' => 1,
'nome' => 'Lorem ipsum dolor sit amet',
...
);
}
Model:
class AdminTestCase extends CakeTestCase {
var $fixtures = array('app.admin');
function startTest() {
$this->Admin =& ClassRegistry::init('Admin');
}
function endTest() {
unset($this->Admin);
ClassRegistry::flush();
}
}
What can be wrong?
when you create the $test database connection, cake should be trying to make the tables in that database. according to your code that should be in tests_clubpets. make sure the database is created and that the user has permissions on the table. also check you did not make any typeo's
if you want to have your tables named test_{tablename}, use:
'prefix' => 'test_',

PHP and Zend framework

How can I easily implement queries in Zend framework?
Check this document:
Zend Framework Database Quick Start (PDF)
You can use doctrine2
Doctrine project.
There is a module compatibile with ZF3
DoctrineModule.
You can use QueryBuilder that brings creation of query to object manipulation.
You can use the Zend Db Adapter object like so:
$sql = 'SELECT * FROM bugs WHERE bug_id = ?';
$result = $db->fetchAll($sql, 2);
Use Zend_Db and just create a $db Object using Zend_Db Factory Method, and then create SQL Statements using Zend_Db_Select Class and pass the $select SQL Statement to the fetch* (fetchRow, fetchAll...) Methods.
1.Config:
config/autoload/dbAdapter.local.php
<?php
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=name;host=localhost',
'username' => 'root',
'password' => 'root',
),
'service_manager' => array(
'abstract_factories' => array(
'Zend\Db\Adapter\AdapterAbstractServiceFactory',
),
),
);
Implementation:
public function testAction()
{
$username = 'user';
$sql = "SELECT email FROM users WHERE username = ?";
$statement = $this->getDbAdapter()->createStatement($sql, array($username));
$result = $statement->execute()->current();
}
protected function getDbAdapter()
{
if($this->dbAdapter == null) {
$this->dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
}
return $this->dbAdapter;
}
Zend framework has abstract_factories ,it allows us to handle multiple DB queries :
Zend\Db\Adapter\AdapterAbstractServiceFactory
Need to set Service Manager :
'service_manager' => array(
'abstract_factories' => array(
'Zend\Db\Adapter\AdapterAbstractServiceFactory',
),
),
Configure adapters in config/autoload/local.php
db' => array(
'adapters' => array(
'database1' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=userDB;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
etc...
),
),
Configure adapters in config/autoload/global.php
return array(
'db' => array(
'adapters' => array(
'database1' => array(
'username' => 'root',
'password' => '',
),
),
),
);
Call adapters
$dbmanager->get('database1');
Use in Model
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Adapter\Adapter;
class UserTable extends AbstractTableGateway
{
protected $table ='user';
public function __invoke(Adapter $adapter)
{
$this->adapter = $adapter;
$this->initialize();
}
public function fetchAll()
{
$resultSet = $this->select();
return $resultSet->toArray();
}
}

Categories