CodeIgniter how to set autoloaded database dynamically - php

Using CodeIgniter 3, I autoload my database config, now how do I change the database connected dynamically ? I was thinking like using session to pass the database value, but session cannot be used in the database config file.
I know I can manually load database and change it, but then I have to call and load the database in every controller and I have tons of the controller, therefore I would like to avoid setting the database manually.

There is probably more than one way to do what you want. The solution shown here uses CodeIgniter’s "Hooks" feature. Specifically, it uses the "post_controller_constructor" hook to match the name of a controller with a specific database configuration defined in database.php.
After the hook does its work the application can make calls to the database in the typical CI way using $this->db->. For example...
$query = $this->db->get('mytable');
This solution is based on the assumption that only one database connection is need for any given controller. This means that all methods in that controller (or any models loaded by the controller) use the same connection.
Here's how it is done.
In application/config/config.php
$config['enable_hooks'] = TRUE;
In application/config/hooks.php
$hook['post_controller_constructor'][] = array(
'class' => '',
'function' => 'set_db_connection',
'filename' => 'post_controller_hook.php',
'filepath' => 'hooks'
);
The file post_controller_hook.php is where the work gets done. It uses lists of controller names to determine which database config is to be loaded.
The list ($controller_lists) contains sub-arrays which group controller names by the db configuration needed. A search is done through each sub-array to find the matching controller name. When a controller name is found the key of that sub-array is the db config to be loaded. If no match is found the 'default' config is used.
The $controller_lists array is hard-coded here but it could easily be loaded from a config file instead. A config file might make maintaining the lists easier.
file application/config/post_controller_hook.php
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$loadConfig = 'default'; //if nothing found in lists we're still good
$controller_lists = array(
'config2' => ['profile'],
'config3' => ['discusion', 'home'],
'config4' => ['suppliers', 'customers', 'inventory', 'orders']
);
foreach($controller_lists as $config_name => $list)
{
if(in_array($controller, $list))
{
$loadConfig = $config_name;
break;
}
}
$CI->load->database($loadConfig);
}
The ability to not load a database for controllers that don't need one could be added if that was desirable. But I'm not going there.
As stated earlier, this solution uses the assumption that only one database configuration (connection) is used for any given controller. If certain methods of a controller need to use a different db configuration this solution becomes more complicated.
Adding the method to the search is easy. The first few lines of set_db_connection() would look like this.
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$method = $CI->router->method;
if($method !== 'index')
{
$controller .= '/'.$method; //append method name
}
$loadConfig = 'default'; //if nothing found in lists we're still good
So now $controller will hold either 'controller/method', or just 'controller' if index() is to being called.
Consider a controller called Viewstate with three methods
class Viewstate extends CI_Controller
{
public function index(){
//uses db 'config4'
}
public function report(){
//uses db 'Config2'
}
public function process(){
//uses db 'Config3'
}
}
We have to include each 'viewstate/method' in the sub-arrays like this.
$controller_lists = array(
'config2' => ['profile', 'viewstate/report'],
'config3' => ['disscusion', 'home', 'viewstate/process'],
'config4' => ['viewstate', 'customers', 'inventory', 'orders']
);
//the rest of the function is as shown earlier
Any 'viewstate/method' not in the search lists it will be assigned the 'default' db config. So it's easy to sort the various needs of viewstate.
The problem is that every 'controller/method' in the site must now be included in the search lists. If the Profile controller has ten methods every combination must now be in the config2 sub-array. So if there are lots of controllers and controller/methods this solution is a poor choice. There might be an elegant way around this problem but that's probably a topic for a new question.

Related

Reusable code for controller in Cakephp3

I would like to create reusable code in controller in "Cakephp way". I would like to replace always one field in few controllers before render website. For example I would like to replace string in field "body". I can do this like this in show method:
public function show($id = null) {
$site = $this->Sites->findById($id)->first();
$new_value = 'test2';
$site['body'] = str_replace('test', $new_value, $site['body']);
}
Is there any better way to do this in cakephp way for example in initalize method or beforeRender? I can't use behavior here.
EDIT:
I know about components, but how to use it to replace all $site['body] (in my code) for all controller methods (so I would like to do this automatic, like behavior for entity)?
Read about Components.
Components are packages of logic that are shared between controllers. CakePHP comes with a fantastic set of core components you can use to aid in various common tasks. You can also create your own components. If you find yourself wanting to copy and paste things between controllers, you should consider creating your own component to contain the functionality. Creating components keeps controller code clean and allows you to reuse code between different controllers.
And see Component Callbacks.
You can use component
https://book.cakephp.org/3.0/en/controllers/components.html
Don't forget to load it in appController or where your need it
After edit :
#nexequ
Maybe if you set the beforeRender in your appController
public function beforeRender()
{
debug($this->request);
}
In $this->request->data array you have your data to replace.
Exemple:
data => array(
'Reunion' => array(
'begin' => '2017-01-13 20:00:00',
'end' => '2017-01-13 20:30:00'
)
If you find the way to get the model ("Reunion" in my example.)
You can do a trick like
replace --> $this->request->data[$model]['body']
I found solution with burzum help, I can use virtual property in src/Model/Entity:
protected function _getBody() {
$new_value = 'test2';
$test = str_replace('test2', $new_value, $this->_properties['body']);
return $test;
}
It will replace for instance 'test2' with $new_value in all controller methods.

phalcon Dynamic Class creation fails with error?

I am trying to create dynimic model class with a model function inside the user model, however for some reason it unable to identify the class location. it gives me an error
Fatal error: Class 'VarEducation' not found
below is the function
public function partner()
{
$view_service=new ViewService();
$partner_vars_check=$view_service->getUserVarPartnerModelCheckMappingArray();
foreach($partner_vars_check as $partner_key=>$partner_var){
$table= str_replace("p_","var_",$partner_key);
$sql = "SELECT * FROM ".$table."
WHERE id='" . $this->{$partner_key} . "'
ORDER BY id DESC";
$partner_obj = new $partner_var();
$this->{$partner_key} = new \Phalcon\Mvc\Model\Resultset\Simple(null, $partner_obj,
$partner_obj->getReadConnection()->query($sql));
}
}
Any idea on what causing this ?
data supplied by the view service is as below
public function getUserVarPartnerModelCheckMappingArray() {
return array(
'p_education' => 'VarEducation',
'p_body' => 'VarBody',
'p_ethnicity' => 'VarEthnicity',
'p_religion' => 'VarReligion',
'p_family' => 'VarFamily',
);
}
Whatever you do, if you have model hidden in any namespace, you have to deliver full namespaces with your mapping array. Lets say, all your models are in namespace of Application\Models, you have to use it this way:
public function getUserVarPartnerModelCheckMappingArray() {
return array(
'p_education' => '\Application\Models\VarEducation',
'p_body' => '\Application\Models\VarBody',
'p_ethnicity' => '\Application\Models\VarEthnicity',
'p_religion' => '\Application\Models\VarReligion',
'p_family' => '\Application\Models\VarFamily',
);
}
Not saying that I would rather switch/case over such a small amount of options and use models directly, instead of building SQL by hand.
Phalcon should load classes correctly even if the class name is a variable. Try isolating the problem down to just:
$myClass='VarEducation';
$partner_obj = new $myClass();
Also try commenting out that particular model from your model list and see if your other models work, perhaps there's an error in finding that particular model but not others. Check your /app/config/loader.php file and make sure you're registering your models directory. Then make sure the file VarEducation.php exists in your models directory as well as your other models. Also make sure that the class name inside the file matches the file name, and you've named the class VarEducation. Also make sure it's not namespaced. Also make sure you didn't forget the leading <?php and that the file doesn't have any errors. If all else fails, you can simply load the class from your loader.php file with something like:
$loader->registerClasses(array(
'VarEducation' => $config->application->modelsDir.'/VarEducation.php'
));
Make sure a simple test for the model works: $x=new VarEducation(); I suggest doing this from your index controller under a test action. If none of this works, comment on this post with your results when trying my suggestions and I'll update my answer.

How to run no action and a single variable in the Silverstripe url_handlers?

I'm working on a project which has a special page type which checks a single 250 alphanumeric string.
Ideally I'd like the URL structure to work like this:
http://www.example.com/check/I1gdTVUsnezY9SDI8V0GS2mg7Y0IdG6MqjCZ8t1yejRdi0pKzyr7G28iF0fyxOW9Le9vg3op7NnuCE0unT7d09aN00Trn7xPYAjLRhqQ9k5aRlsThsTk0HaS966MCDb4aC23RW4Cl273e9YiWKFNm2STI75X1jnlZ684M7ejDpmWg1YfM32OpwX066bF5VTp5v0F5I42T2SWh8QhMc9GW9I2ZbuP7ykh710UHnLwQyA3BO7KitZWcCU0u9
However using allowed_actions and url_handlers the standard way I can only get it to work if I preface the alphanumeric string with "uid" - http://www.example.com/check/uid/string-goes-here
class CheckPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'uid'
);
private static $url_handlers = array(
'uid/$uID' => 'uid'
);
Is it possible to have url_handlers work with just a variable and no action on a custom page?
You can define a route without a action in your _config.php:
Director::addRules(100, array(
'check/$UID' => 'CheckPage_Controller'
));
and in your CheckPage_Controller you can catch the request in the index function:
public function index() {
var_dump($this->request->allParams());die;
}
But you should be aware that the add route catches all requests to /check/whatever. So you need to define a different url for other stuff.
Assuming you have a pagetype "CheckPage" with $URLSegment "check" and some Dataobjects in a has_many relation you can use Nightjars extension for using this urlslug method, which is really elegant:
https://github.com/NightJar/silverstripe-slug/
ATM it's no ready to install module but an extension to the controller you can configure.
If you need any further help please provide some more informations about your code structure.

Magento order view button for id process

I must place a button in order view that will send further the order id. The id will be used for a simple database query then it should return to order view.
I found out how to create the button, I created app/code/local/Mage/Adminhtml/Block/Sales/Order/View.php following the view.php from core.
Here is my code :
class Mage_Adminhtml_Block_Sales_Order_View extends
Mage_Adminhtml_Block_Widget_Form_Container
{
public function __construct()
{
$this->_objectId = 'order_id';
$this->_controller = 'sales_order';
$this->_mode = 'view';
parent::__construct();
$this->_removeButton('delete');
$this->_removeButton('reset');
$this->_removeButton('save');
$this->setId('sales_order_view');
$order = $this->getOrder();
$this->_addButton('release_payment', array(
'label' => Mage::helper('sales')->__('Release Payment'),
'onclick' => 'setLocation(\'' . $this->getUrl('*/*/release') . '\')',
'class' => 'go'
));
}
What I want is that this location that should be sales_order/release to actually do something with the order id. I tried to understand how but I can't manage to actually create the controller.
You should probably collect those overrides into a module. The ugly method would otherwise be to copy
app/code/core/Mage/Adminhtml/controllers/OrderController.php
to
app/code/local/Mage/Adminhtml/controllers/OrderController.php
and add the releaseAction()-function to it.
For the more elegant solution you would need to create a module with config options in it's etc/config.xml file to override/overload the specified block and controller code. With this method your changes are better contained and it's easy to switch the module on/off. Also when extending the original block/controller you don't need to include anything other then the modified/added methods, the rest will be executed from the original file.
Actually even the main documentation from Magento has decent examples:
http://www.magentocommerce.com/wiki/5_-_modules_and_development/0_-_module_development_in_magento/how_to_overload_a_controller
And Googling for magento controller overloadgives good results too, so I won't give an lengthy example right here.

How to pass multiple parameters to a library in CodeIgniter?

I am making a library for CodeIgniter, and I wish to pass multiple parameters of different types (a PDO object, username and password, configurations, etc).
I know I can pass an array of all of these things, but that doesn't seem to be the best way of doing things (as $params can't ever describe what is needed).
How can I pass multiple parameters to a library?
Thanks in advance.
There are several approaches to this particular problem. I'll list (in preferred order) ways I know to solve it:
Associative Array Arguments:
This approach is pretty flexible, as the order of the parameters doesn't matter, and it resolves a pretty big complaint many have with how PHP defines function parameters. You simply pass in the "non-default" parameters you want. This is probably the most "codeigniterish" way to do it, if that's even a thing.
class MyLibrary {
public function __construct($params = array())
{
// Merge default parameter names and values,
// with given $params array
$params = array_merge(array(
'server' => 'myserver',
'database' => 'mydatabase',
'username' => 'myuser',
'password' => 'mypassword'
), $params);
// Create variables from parameter list
extract($params);
var_dump($server);
var_dump($database);
var_dump($username);
var_dump($password);
}
}
// Initialization:
$this->load->library('mylibrary', array(
'server' => 'server-arg1',
'database' => 'database-arg2'
));
Numbered Arguments:
This approach replicates the typical PHP parameter paradigm (defines names, orders, and default values for all expected parameters).
class MyLibrary {
public function __construct($params = array())
{
// Add relevant defaults to missing parameters
$params = array_merge($params, array_slice(array(
'myserver',
'mydatabase',
'myuser',
'mypassword'
), count($params)));
// Create variables from parameter list
extract(array_combine(array(
'server',
'database',
'username',
'password'
), $params));
var_dump($server);
var_dump($database);
var_dump($username);
var_dump($password);
}
}
// Initialization:
$this->load->library('mylibrary', array('server-arg1', 'database-arg2'));
Override the CI Loader class:
This is AT YOUR OWN RISK. Basically, the CI_Loader::_ci_init_class() method needs to be overridden with a MY_Loader class and corresponding method. These are the lines that you "don't like" (lines 1003-1012 in my install):
// Instantiate the class
$CI =& get_instance();
if ($config !== NULL)
{
$CI->$classvar = new $name($config);
}
else
{
$CI->$classvar = new $name;
}
The "safest" replacement that I could guess would be this:
// Instantiate the class
$CI =& get_instance();
if (isset($config[1])
{
// With numeric keys, it makes sense to assume this is
// is an ordered parameter list
$rc = new ReflectionClass($name);
$CI->$classvar = $rc->newInstanceArgs($config);
}
elseif ($config !== NULL)
{
// Otherwise, the default CI approach is probably good
$CI->$classvar = new $name($config);
}
else
{
// With no parameters, it's moot
$CI->$classvar = new $name;
}
I really don't know how many things this will break, but I can almost certainly say there will be something. It's not really worth the risk. I'd STRONGLY recommend the first approach above.
Cheers!
One can sidestep Codeigniters Loader class and instantiate objects directly. APPPATH is a Codeigniter constant.
require_once( APPPATH.'libraries/some_class.php' );
$this->Some_class = new Some_class( param_1, param_2, param_n );
$this->Some_class->do_something();
Usage is the same as if you'd loaded the Library with Codeigniters Loader class.
$this->load->library( 'some_class' )
$this->Some_class->do_something();
I personally don't like passing arrays to my classes becasue then you have to validate the contents. PHP can take care of this for you when the parameters are passed to the __construct() individually.
From my experience with CodeIgniter, unless you modify the loader class to act differently (as you might know, in application/core/ folder is where you have to implement you custom class) there is no way (no one of that I know)
I use many external libraries (mostly api sdks or sparks) and I like to build my own config files where to set values that will be loaded into libraries when called upon them, so when I need to load libraries I just build a simple or multidimensional $params = array() according to my needs and then work with it inside library.
So in short answer, $this->load->library('lib_name', $params) is the only way I am aware of.
By default in CodeIgniter you can't. You can only pass a single param to your library classes. As stated before that param could be an array that contains all the other parameters and you could just use it as $param[0], $param[1]....
But I agree with you, it's kinda strange and ugly to see.
If you have PHP5+ there's the ReflectionClass that could help you with this but you should edit the source code of CodeIgniter and implement this function. The function you are looking for is ReflectionClass::newInstance() that you can find here.
use $this->upload->initialize($configUpload); after $this->load->library('upload', $configUpload);
this is best way:create a big array
$data1=array('item1','item2','item3');
$arr=array('data1'=>$data1,'data2'=>'item4','data3'=>'item5');//create big array of datas
extract($arr);//convert that to variables again after send to library
//use that again
print_r($data);
echo $data2;
echo $data3;

Categories