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;
Related
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.
In CodeIgniter, when creating and calling a custom library, it is possible to pass variables (parameters) to the library via a second optional variable.
Example taken from the documentation:
$params = array('type' => 'large', 'color' => 'red');
$this->load->library('someclass', $params);
and
class Someclass {
public function __construct($params)
{
// Do something with $params
}
}
In some cases, it may be desirable to reload the library with a different set of parameters. However, this does not seem to be possible. When the [...]->library() method is called again with a new set of parameters, only the original parameters are expressed—not the new ones. The CodeIgniter documentation does not address this issue.
What are my options?
Can I do this in a Controller:
$this->User->read(null, $id);
$this->User->find('list');
Is it correct?
Am I using MVC correctly?
Can these easy functions be used in a Controller? Or, do I need to create these functions in the Model? Like Model->getUser(), and have that function use Model->read().
I know that functions it's called by Model, but, when I want pass some parameters, and function makes big, for example:
$this->User->find('all', array(
'conditions' => array(
'User.active' => true,
'User.group_id' => 3,
'User.age >=' => 18
)
));
Can I call this function in Controller, or need create a custom function in Model, to call it? Like... $this->User->findSomeCustomFunction($param1, $param2, $param3)?
TLDR:
It's "ok" to call a find() from your Controller, however best practice is to put any/all find()s in your models.
If you make a habit of putting all your find()s in your models, it will make it much easier to maintain your code in the long run.
Explanation/example:
In this case, as an example, you could start with a seemingly simple function:
//User model
public function getUsers() {
return $this->find('list');
}
But later, maybe you need something more along the lines of:
//User model
public function getUsers($opts = array()) {
$defaults = array(
'findType' => 'all',
'activeOnly' => true,
);
$params = array_merge($defaults, $opts);
$qOpts = array('conditions' => array());
//active only
if(!empty($params['activeOnly'])) $conditions[$this->alias.'.active'] = 1;
return $this->find($params['findType'], $qOpts);
}
(Pardon if there are many ways to make that code better - it was just off the top of my head - It gives you the idea.)
Keeping all your find()s in the Model also keeps you from having to search through each Controller every time you want to write a find() to determine if you've used a similar find() anywhere else. If you're programming as a team, that can be a nightmare, and you're almost guaranteed to be duplicating code.
It is perfectly fine to call Model->find() from a Controller. However, you will also want follow the DRY (Don't Repeat Yourself) principles. That basically means "Don't copy-paste code everywhere."
So, if you find that you need to make this exact Model->find() call from many Controller actions, it is considered good practice to abstract it into a function call against the Model. So yes, your Controllers would then call $this->User->findSomeCustomFunction().
I am currently working on a mashup that incorporates many data feeds. In order to display ALL of the feeds that the user wants on one page, I am currently using if statements to cross-check with the MySQL database like this:
if($var["type"]=="weather")
$var being the result of a call to mysqli_fetch_array
and then including code relevant to the function (e.g. weather) underneath, and then another "if" statement for another feed, so on so on. The problem is that there will be many feeds, and having all these "if" statements will be slow and redundant.
Is there any way to optimize this PHP code?
Another solution might be to map the "type" to a custom function using associative arrays.
e.g. (pseudo code)
function handle_wheater_logic() {
// ... your code goes here
}
function handle_news_logic() {
// .. your code goes here
}
$customFunctions = array("wheater" => "handle_wheater_logic", "news" => "handle_news_logic");
while ($row = mysql_fetch_...) {
call_user_func ($customFunctions[$row["type"]])
}
This would eliminate the need to use a lot of if statements. You might as well do the "type to function" mapping in a configuration file or maybe just store the name of the custom function to call for each "type" in a database table - that's up to you.
You can, of course also pass parameters to custom function. Just checkout the documentation for call_user_func[_array].
Try this:
$methods = array(
"weather" => function() {
// code
},
"otheroption" => function() {
}
);
Just use then $var["type"] as a index in the array to get the function:
$methods[$var["type"]]();
You can obviuosly, for better readbility do something similar:
$methods = array(
"weather" => "wheater_function",
"otheroption" => "other_function"
);
and then call the functions this way:
call_user_func($methods[$var["type"]]);
To be even more object oriented we can obviously store in the array objects implementing a particular interface, or store object redifining the __call() magic method and use it like functions.
You can use a switch statement.
A good solution for eliminating a lot of if statements and a huge switch statement just checking for one condition, would be to implement a design pattern such as the Strategy pattern.
This way you will have the code for each type separated, which makes it easier to overview and manage.
Here's an example of an implementation http://blogs.microsoft.co.il/blogs/gilf/archive/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements.aspx
Even if you won't implement this strictly it will give you some ideas on how to solve this elegantly.
Create an array that associates a function to each feed type:
$actions = array("weather" => "getWeather",
"news" => "getNews");
Then use call_user_func to call the correct one:
call_user_func($actions[$var["type"]]);
Polymorphysm for the rescue.
inteface FeedInterface {
public function retrieve($params);
}
class FeedWeather implements FeedInterface {
public function retrieve($params) {
//retrieve logic for weather feed
}
}
class FeedSports implements FeedInterface {
public function retrieve($params) {
//retrieve logic for sports feed
}
}
With use of PHP class autoloading, each of above declarations can be in a separate file, possibly namespaced as well. Then your feed retrieval code could look like this:
$class = 'Feed'.$var["type"];
$feed = new $class;
$feed->retrieve($params);
That's overly simplified and would need some additional code for error handling, discovery of non-existing classes and such, but the idea should be clear.
Using either an If Statement or a Switch statement will be faster than you care about. It might look ugly and be cumbersome to maintain but it will be fast.
So, I am looking at a number of ways to store my configuration data. I believe I've narrowed it down to 3 ways:
Just a simple variable
$config = array(
"database" => array(
"host" => "localhost",
"user" => "root",
"pass" => "",
"database" => "test"
)
);
echo $config['database']['host'];
I think that this is just too mutable, where as the configuration options shouldn't be able to be changed.
A Modified Standard Class
class stdDataClass {
// Holds the Data in a Private Array, so it cannot be changed afterwards.
private $data = array();
public function __construct($data)
{
// ......
$this->data = $data;
// .....
}
// Returns the Requested Key
public function __get($key)
{
return $this->data[$key];
}
// Throws an Error as you cannot change the data.
public function __set($key, $value)
{
throw new Exception("Tried to Set Static Variable");
}
}
$config = new stdStaticClass($config_options);
echo $config->database['host'];
Basically, all it does is encapsulates the above array into an object, and makes sure that the object can not be changed.
Or a Static Class
class AppConfig{
public static function getDatabaseInfo()
{
return array(
"host" => "localhost",
"user" => "root",
"pass" => "",
"database" => "test"
);
}
// .. etc ...
}
$config = AppConfig::getDatabaseInfo();
echo $config['host'];
This provides the ultimate immutability, but it also means that I would have to go in and manually edit the class whenever I wanted to change the data.
Which of the above do you think would be best to store configuration options in? Or is there a better way?
Of those 3 options, a static method is probably the best.
Really, though, "the best" is ultimately about what's easiest and most consistent for you to use. If the rest of your app isn't using any OO code then you might as well go with option #1. If you are ultimately wanting to write a whole db abstraction layer, option #2.
Without knowing something more about what your goals are and what the rest of your app looks like, it's kind of like asking someone what the best motor vehicle is -- it's a different answer depending on whether you're looking for a sports car, a cargo truck, or a motorcycle.
I'd go with whats behind door #3.
It looks easier to read and understand than #2, and seems to meet your needs better than #1.
Take a look at this question for ideas on storing the config data in a separate file:
Fastest way to store easily editable config data in PHP?
I'd use method #2 pulling the config data as an array from an external file.
The best way is that which fits your application best.
For a small app, it might be totally sufficient to use an array, even it is mutable. If no one is there to modify it except you, it doesn't have to be immutable.
The second approach is very flexible. It encapsulates data, but does not know anything about it. You can pass it around freely and consuming classes can take from it what they need. It is generic enough to be reused and it does not couple the config class to the concrete application. You could also use an interface with this or similar classes to allow for type hints in your method signatures to indicate a Config is required. Just don't name it stdDataClass, but name it by it's role: Config.
Your third solution is very concrete. It hardcodes a lot of assumptions about what your application requires into the class and it also makes it the responsibility of the class to know and provide this data through getters and setters. Depending on the amount of components requiring configuration, you might end up with a lot of specific getters. Chances are pretty good you will have to rewrite the entire thing for your next app, just because your next app has different components.
I'd go with the second approach. Also, have a look at Zend_Config, as it meets all your requirements already and let's you init the Config object from XML, Ini and plain arrays.