How can I write a function which has lots of arguments better? - php

I have a function like this:
public function myfunc ($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8) {
// do something
}
See? My function gets 8 arguments. Yes it works as well, but you know, it's ugly kinda ..! Isn't there any better idea? For example passing just one array contains all argument. Is it possible? or even something similar.

I do it this way..
$params = [];
put things in params..
$params[] = $a;
$params[] = $b;
pass the array to function
myFunction($params);
The function accept array as arg like, definition:
public function myFunction($params = []){}
Pass something in, and var_dump to check for yourself...

OK, now I know that it's an SQL operation you're doing then the best approach would be an associative array (assuming PDO and prepared statements).
public function myFunc (array $data)
{
// Using 3 values for example!
$stmt = $this -> pdo -> prepare ("INSERT INTO TABLE thisTable (
col1,
col2,
col3
) VALUES (
:val1,
:val2,
:val3
)");
if (false !== $stmt execute ($data))
{
return $stmt -> rowCount ();
} else {
return 0;
}
}
You'd call it with an array with the correct parameters in it:
$success = (bool) $obj -> myFunc (["val1" => "First value to insert",
"val2" => "Second value to insert",
"val3" => "Third value to insert"]);

It depends whether myfunc belongs to an exposed api or not (i.e.: public)
If it is public, the signature must break when you update the underlying model (your insert query), otherwise errors will be committed on the client-side.
With an array, you're losely mapping your Model to your Application, and you'd just expect programmers to send you the right values. With a tight/restricting mapping, this kind of error cannot happen.
I think that if you're saving an item in the database, you actually need all these fields. It is unelegant indeed, but it's not an anti-pattern as none of them are optional. And even if one or two were, it would not be a concern.
What you could do to improve your api is either:
If there are situation where you need to pass only a few of the parameters (i.e. most are optional or depend on a particular scenario) then you could specialize the method into separate functions. But PHP not accepting function polymorphism is quite a pain in the neck for this kind of things; you'd have to name the methods differently.
public function myfunctosavedatainaparticularcase ($arg1, $arg2, $arg3, $arg4)
// do something
}
public function myfunctosavedatainanotherparticularcase ($arg5, $arg6, $arg7, $arg8)
// do something
}
Use an object model mapper. For example, suppose you're saving a User data. You'd just pass a User object to the method:
public function myfunc (User $user)
// map fields to the User signature.
}
This would be acceptable if you're in control of the User class since you'd have to change it to reflect model changes.
Use an ORM to handle this for you. You'd only have to update the xml specifications of the schema after you decide to change the database model, all necessary changes would be propagated to the application automatically. Of course the objects definition would change but that is inevitable.

Related

How can i access a variable outside that function in codeigniter

I am sorry for asking this basic level question. I have fetched some data from DataBase and stored it to a variable inside a function, and wanted to get the value of that variable outside the function?
public function getemailData()
{
$email_id_investors = $this->db
->select('value')
->get_where('common_email_settings', ['name' => investors_email])
->row()->value;
}
I wish to get the value of the $email_id_investors outside the function. Again I am apologizing for this basic question
Database table name - common_email_settings
fields are Id, title, name, value
1 Greeting Mail, greeting_email ,Greetings#investorsloanservicing.com
2 Loan Service Mail, loan_service_email ,LoanServicing#investorsloanservicing.com
3 Processing Mail, processing_email ,processing#investorsloanservicing.com
To strictly answer the question, you could store the value in a scoped global $this variable, though I don't know why you wouldn't just query the function and have it return a value.
public function getemailData($investors_email)
{
$this->email_id_investors = $this->db
->select('value')
->get_where('common_email_settings', ['name' => $investors_email])
->row()->value;
}
// then in another function called later in the chain, you can grab it
public function doSomethingElse() {
$investors = $this->email_id_investors;
}
It's probably better just to create a getter function for that variable.
This doesn't look useful given your scenario. This might be useful if the variable you're storing is something processor intensive (and you're using $this like a cache), you need to access it in multiple functions called during a given state, and you don't want to rewrite your function chain to accept this parameter (so as to pass it along). However, that is a clear sign you need to refactor your logic and make it more flexible (pass object or arrays rather than single variables for example).
You are not returning your variable.
Try returning your variable like this,
public function getemailData()
{
$email_id_investors = $this->db
->select('value')
->get_where('common_email_settings', ['name' => investors_email])
->row()->value;
return $email_id_investors;
}
function getemailData($name)
{
$email_id_investors = $this->db
->get_where('common_email_settings', ['name' => $name])
->result()[0];
return $email_id_investors->value;
}
This one worked for me. I have given this function in the common model page and called this on other pages.Thank you for your help
$email = $this->common->getemailData('account_email'); -> getting data in this variable
echo $email;
// exit();

MVC/PDO : how to build a model using PDO's prepared statements syntax?

I want to build a model class for my PHP application. It will have methods meant to select/update/insert/delete specific data from a database according to the method's parameters. I only want to use prepared statements.
Here is an overview of what the class should look like :
class Database {
private $_db;
// Stores a PDO object (the connection with the database) within the $_db property
public function __construct($host, $user, $password) {...}
public function select() {...}
public function update() {...}
public function insert() {...}
public function delete() {...}
}
The problem is that I don't really know how to do this. Let's say I want to select everything from the table "farm" where the animal is a dog. The syntax for this statement would be the following :
$animal = 'dog';
$query = $this->_db->prepare('SELECT * FROM farm WHERE animal = :animal');
$query->execute(array(':animal' => $animal));
$result_set = $query->fetchAll();
This is very complicated to implement within a class method. As you can see, I call the execute() method but I don't even know in advance if the WHERE clause will be used !
And even worse : what if I will want to use, let's say, the LIMIT x, y clause later on ?
Which parameters should I ask for and how to treat them ? Should I simply require the parameters to be one query + multiple variables that will be passed to the execute() method ?
Are these types methods reasonable for what I want to do ? Maybe I should to a dedicated method for each MySQL query the application will perform, but this is quite complicated because it's a big database and a big application.
What do you guys think ?
Thanks in advance :P
Your API looks pretty useless to me, because as I see it it's just a wrapper around PDO. What do you gain by wrapping PDO like that?
Instead it would probably make more sense to have your object actually representing things, e.g.:
namespace Project\Storage\Database;
class Farm
{
private $pdo;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
public function getAnimalsByType(string $animalType): AnimalCollection
{
$stmt = $this->pdo->prepare('SELECT * FROM farm WHERE animal = :animal');
$stmt->execute([
'animal' => $animalType,
]);
// alternatively use a factory to build this to prevent tight coupling
return new AnimalCollection($stmt->fetchAll());
}
}
On a side note: forget about MVC in PHP (it's not even possible). Just focus on the more important separation of concerns.
Maybe I should to a dedicated method for each MySQL query the application will
perform, but this is quite complicated because it's a big database and
a big application.
Yes, this is an easy way to organize your database access.
But you should not put ALL of them in the same class. You should separate your classes by their domain.
class animalRepository {
// ...
public function getAnimalByName($animal){
$query = $this->_db->prepare('SELECT * FROM farm WHERE animal = :animal');
$query->execute(array(':animal' => $animal));
$result_set = $query->fetchAll();
// ...
}
}
To make this communicate more clearly you could call those classes repositories, as they are storing the data for the specific domain.
Another common name would be mappers, because they are mapping the data to your objects.
Very opinionated answer. Anyway:
PDO's Prepared Statements are a little more capable than being created and calling execute on them. How you would usually do this is by first building your query and then binding the values:
$querystring = 'SELECT * FROM farm';
$args = array();
if($animal != '') {
$querystring .= 'WHERE animal = :animal';
$args[':animal'] = $animal;
}
$query = $this->_db->prepare($querystring);
$result = $query->execute($args)
if($result !== false) {
// fetch ...
} else {
// error output / return val
}
This is the general idea. Depending on your input parameters you build a query. It will probably become more sophisticated than that, for example filling a $where = array() and then you add to the $where[] = ... your where conditions and in the end you just join them all together with sql AND:
$this->_db->prepare($querystring.
( count($where) > 0 // the > 0 is redundant btw
? 'WHERE '.implode('AND',$where)
: '' )
);
You might similar things with joined tables, select statements and the like. It can get very complex. It's probably wise to mix this approach with separating at sensible points with Philipp's answer/approach.

OOP shortening class

This is not so much a question about execution as it is a question about improving code. I am a 2nd year student, we started to touch on OOP recently and I am finally getting the hold of it....sort of.
I realize this is a very basic question, but what better place to learn from some of the best.
My Question
I have a class which creates a new match. My problem is that I am sure the code is unnecessary long and can get much improved (just keep in mind it is beginner level).Specifically I would like to know:
Can I change the below into 1 setter and 1 getter method?
I would like to use the rand() function for match ID can I do this inside the setter function of setMatchId or should it be done outside of the class?
Thank you very much for taking the time to read this.
<?php
class match{
private $matchId;
private $team1;
private $team2;
private $venue;
function __construct($pMatchId, $pTeam1, $pTeam2, $pVenue){
$this->matchId = $pMatchId;
$this->team1 = $pTeam1;
$this->team2 = $pTeam2;
$this->venue = $pVenue;
}
function setMatchId($pMatchId){
$this->matchId = $pMatchId;
}
function getMatchId(){
return $this->matchId;
}
function setTeam1($pTeam1){
$this->team1 = $pTeam1;
}
function getTeam1(){
return $this->team1;
}
function setTeam2($pTeam2){
$this->team2 = $pTeam2;
}
function getTeam2(){
return $this->team2;
}
function setVenue($pVenue){
$this->venue = $pVenue;
}
function getVenue(){
return $this->venue;
}
} // c;lass match
$x = new match("1", "Patriots", "Chargers", "Newlands");
echo $x->getMatchId();
echo'<br />';
echo $x->getTeam1();
echo'<br />';
echo $x->getTeam2();
echo'<br />';
echo $x->getVenue();
?>
How often are teams or venues going to change for a match? I think you should get rid of the setters since you're already providing all the necessary data through your constructor.
You can indeed change your code to work with a single getter and setter methods, but I'd strongly discourage that. IDE's won't be able to assist you with code completion if you implement such methods but, most importantly, you should never blindly implement getters and setters in your entities if they have no reason to exist.
Let the design guide you on that. Start by passing everything your objects need through their constructors and only add getters/setters when you need them, not the other way around.
In terms of the randomness of the ID, you could use UUIDs for them. You could use this library to create them. I'd pass them through its constructor as well.
You can use __set and __get magic methods of PHP.
private $data = array(); // define property array
public function __set($name, $value) // set key and value in data property
{
echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}
public function __get($name) // get propery value
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
}
You can write your existing code as below:-
class Match{
private $data = [];
function __construct($property=[]){
if(!empty($property)){
foreach($property as $key=>$value){
$this->__set($key,$value);
}
}
}
public function __set($name, $value) // set key and value in data property
{
// echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}
public function __get($name) // get propery value
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
}
}
Set properties using construct method
$x = new match(["matchId"=>"1", "team1"=>"Patriots","team2"=>"Chargers","venue"=>"Newlands"]);
echo '<pre>'; print_r($x);
Set properties without construct method
$x = new match;
$x->matchId = '1'; // 1
$x->team1 = 'team1'; // Patriots
$x->team2 = 'Chargers'; // Chargers
$x->venue = 'Newlands'; // Newlands
echo '<pre>'; print_r($x);
output:-
Match Object
(
[data:Match:private] => Array
(
[matchId] => 1
[team1] => Patriots
[team2] => Chargers
[venue] => Newlands
)
)
Now you can access and set propery by below way:-
// Get all properties values
echo $x->matchId; // 1
echo $x->team1; // Patriots
echo $x->team2; // Chargers
echo $x->venue; // Newlands
// Overwrite existing values
$x->team1 = 'new team1';
// Get updated value
echo $x->team1; // new team1
Hope it will help you :)
The first question:
Can I change the below into 1 setter and 1 getter method?
[EDIT] Reply to first comment:
You can, but you shouldn't.. To me it's better keep all setters and getters parted. You might want to get only a specific field when using your match object instance in your code. So if you need to get team1 or team2 it's better to have two different getter methods.
The second question:
I would like to use the rand() function for match ID can I do this inside the setter function of setMatchId or should it be done outside of the class?
Well, in my opinion, the best way of handle it is to disallow any access to the $matchId field making it private and removing any setter method.
Then, you should place the rand generation inside the constructor or, if you want to keep it parted in a specific function you could make a public getter like this:
public getMatchId(){
if ($this->matchId != null)
return $this->matchId
// Generate it with rand()
$this->matchId = rand()
return $this->matchId;
}
In the constructor then simply call the getMatchId() method.
By the way, this solution doesn't help you with getting a unique match identifier, to achieve that you should generate it not purely randomly but using something that is dependant of the informations of the Match (for instance you could use a combination of team1, team2 and venue) and/or keep track of used matchid (a static field or a database could be helpful)
[EDIT] Reply to second comment:
I'm using the if statement in the getter because this getter is thought to generate the $matchId when it's called for the first time, while it'll always return the previously generated $matchId for the other calls.
You question made me think of another possible implementation. If you want to avoid the if then you should generate the $matchId in the constructor.
This way should be fine:
public __construct($team1, $team2, $venue){
$this->matchId = rand();
$this->team1 = $team1;
$this->team2 = $team2;
$this->venue = $venue
}
public getMatchId(){
return $this->matchId;
}
There are multiple answers covering how to do setters and getters in various degrees of complexity and magic. In this post I would rather focus on the design quality of your class Match. This is based on the design idea related to what do you want to use your class for?
Some typical statements answering this question:
Keep record of a given match – In other words it needs to hold information related to one match, i.e. venue, homeTeam, awayTeam, result?, and possibly a matchId related to storing the result somewhere
Set the result of a match – You'll create the match, and then a little later you'll set the actual result of the match.
Store a match – If you don't store it anywhere it is kind of futile to keep track of the match, so most likely you would need some interface either to a database, or some mean to get all information related to a match ready for storing into a file or similar
Ability to retrieve the details of a match – If not getting all information at the same time, you could opt for a getter for the specific values you'll want.
For me I don't see the need for changing the team or venue, as that would mean a new match in my world. And I would definitively not implement a generic setter which would allow for setting whatever to whatever. A generic setter is a security risk in my world.
Alternate implementation
Adhering to the statements given I would write something similar to this:
<?php
class Match {
private $matchId;
private $homeTeam;
private $awayTeam;
private $venue;
private $result;
function __construct($venue, $homeTeam, $awayTeam, $matchId = NULL) {
$this->venue = $venue;
$this->homeTeam = $homeTeam;
$this->awayTeam = $awayTeam;
if (is_null($matchId)) {
$this->matchId = uniqid();
} else {
$this->matchId = $matchId;
}
// In PHP7: $this->matchId = $matchId ?: uniqid();
$this->result = "";
}
function setResult($result){
$this->result = $result;
}
function getAll(){
return array($this->venue, $this->homeTeam, $this->awayTeam,
$this->matchId, $this->result);
}
function __get($name) {
if (property_exist($this, $name)) {
return $this->$name;
}
}
function __set($name, $value) {
if (property_exist($this, $name)) {
$this->$name = $value;
}
}
?>
Some comments related to this code:
homeTeam and awayTeam – Having variables name team1 or team2 is a code smell, to me. I would either create an array for those, or find better names. In this case I opted for better names to make a clear distinction between the two variables.
__construct() – When creating a match the default value for matchId indicates that it will be set to a uniqid(). I consider this a better practice rather then using a random value. And it still allows for setting a specific match id if you want to provide this.
Based on the assumption that you don't know the result when the match is created, the result is set to an empty string for starters.
setResult() – As this is the only part of a match I foresee changing I provided a setter for this value.
getAll() – This returns an array of all the values, ready for storing somewhere. If you like this could easily be changed into a comma separated string or whatever format you would like for post-processing. It could even be a dictionary, but I just used a simple array to keep it simple.
__get() and __set() – Contrary to some of the other answers this getter (and setter) is a little safer to use as it verifies that the actual property is defined in this class using property_exist().
I'm not sure if I would actually have the generic setter, but if you'd like one, this is a better option as it doesn't allow for creation of new properties to your class at runtime.
Usage of class
Here is some simple usage of the class (if my untested code actually works, that is):
<!php
$m = new Match("Newlands", "Patriots", "Chargers");
// Time passes
$m->setResult("102-32");
echo 'In the game ' . $m->homeTeam . ' vs ' . $m->awayTeam
echo ' at ' . $m ->venue ' the result was ' . $m->result . ' <br />'
// Append the match to a file
$fp = fopen('allmatches.csv', 'a');
fputcsv($fp, $m->getAll());
fclose($fp);
?>
This uses fputcsv to format the array into a line in the csv format. Having a method or some way to create a match from an array is left as an exercise. You possibly have a static method taking a file name as a parameter, and return an array of matches.
There is no good or bad model when you aren't trying to solve a well-defined problem just like there's no good answer to a bad question.
Before even worrying about things such as getters and setters you need to determine the purpose of the model and what problem it is trying to solve.
I understand that this is probably just a modeling exercise, but if you want it to have any value, start by defining your problem domain and then work out the solution.
For instance, if you are modeling an application service that allows to query a list of matches, then perhaps Match is a simple immutable data structure that acts as a Data Transfer Object.
If you were modeling a ViewModel that is meant to be 2-way bound to a CRUD screen allowing to update the details of a Match then perhaps you'd have a data container with public getters and setters like you had.
If you were crafting a tournament system domain model and had a use case such as: "Tournament administrators will enter the scoring of a match after it's completion. The outcome will be automatically resolved by the system. The possible results are that the home/away team wins or a draw."
Then perhaps Match would carry a behavior such as (pseudo-code):
scoring = new Scoring(homeTeamScore: 2, awayTeamScore: 3);
match.complete(scoring);
match.outcome(); //-> MatchOutcome.AwayTeamWon
As you can see, the model should be a solution to a well-defined problem. It should model the reality of that problem (not the real world), no more, no less.
I would like to use the rand() function for match ID can I do this
inside the setter function of setMatchId or should it be done outside
of the class?
The generation of an entity's identity is usually not the responsibility of the identity itself in respect to the Single Responsibility Principle. The algorithm that generates the identity may change independently of the Match concept itself.
First of all, there's nothing bad in having several get/set methods, unless you're coding on a 64kb RAM machine (Where you probably would use C, Lua, or such instead of PHP). If they're all doing (almost) the same thing and you think they're messing code up, put them on the very end of your class, so they don't block your vision ;-).
For the practical altering of your code:
If you have several members which differ only by data but actually represent the same kind, like team1, team2 puting them into an array and use a get/setByIndex is legit.
(Take care: I didn't use PHP for hundreds of years or so, there might me syntactical mistakes)
Example:
function setTeamByIndex($pIndex, $pTeam){
$this->teams[$pIndex] = $pTeam;
}
function getTeamByIndex($pIndex){
return $this->team[$pIndex];
}
Alternatively, in other language it's common to return multiple values. This is not possible in PHP, but there's a workaround:
setTeamsFromArray
-- receives an Array with teams and applies the given teams by their key.
getAllTeamsArray
-- returns an Array, containing all teams.
function setTeamsFromArray($pTeams){
foreach ($pTeams as $key=>$team) {
$this->teams[$key] = $team
}
}
function getAllTeamsArray(){
return array( $this->team1, $this->team2 )
}
echo(getAllTeamsArray()[0]) -> echos team1
echo(getAllTeamsArray()[1]) -> echos team2
In my opinion, this is all one reasonable could do in your case.
Shrinking stuff down is not always reasonable and 10 4liners are, most of the time, better than 1 40liner.
for geter and seter you can use __call() magic method for example realize the geters and setters
public function __call($name, $arguments)
{
// TODO: Implement __call() method.
$method = substr($name,0,3);
$key = strtolower(substr($name,3,strlen($name)));
if($method == 'set') {
$this->_data[$key] = $argument[0]
return $this;
} elseif($method=='get') {
if(isset($this->_data[$key])) {
return $this->_data[$key];
} else {
return null;
}
}
}
this is simple realization getter and setter automaticaly generate.

Convert a class to a function when there's __construct() elements

I am learning PHP (constantly) and I created some time ago a class that handles translations. I want to emulate gettext but getting the translated strings from a database. However, now that I see it again, I don't like that, being a class, to call it I need to use $Translate->text('String_keyword');. I wouldn't like either to have to use $T->a('String_keyword'); since that's completely not intuitive.
I've been thinking a lot on how to make it to be called with a simple _('String_keyword'), gettext style but, from what I've learned from SO, I haven't been able to find a 'great' way to accomplish this. I need to pass somehow the default language to the function, I don't want to pass it every time I call it as it would be _('String_keyword', $User->get('Language'))). I also don't want to include the user-detection script in the _() function, as it only needs to be run once and not every time.
The easiest one would be to use GLOBALS, but I've learned here that they are completely-utterly forbidden (could this be the only case where I can use them?), then I thought to DEFINE a variable with the user's language like define ( USER_LANGUAGE , $User->get('Language') ), but it seems just to be the same as a global. These are the 2 main options I can see, I know there are some other ways like Dependency Injection but they seem to add just too much complication for a so simple request and I haven't yet had time to dig into them.
I was thinking about creating a wrapper first to test it out. Something like this:
function _($Id, $Arg = null)
{
$Translate = new Translate (USER_LANGUAGE);
return $Translate -> text($Id, $Arg)
}
Here is the translation code. The language is detected before and passed to the object when created.
// Translate text strings
// TO DO: SHOULD, SHOULD change it to PDO! Also, merge the 2 tables into 1
class Translate
{
private $Lang;
function __construct ($Lang)
{
$this->Lang = $Lang;
}
// Clever. Adds the translation so when codding I don't get annoyed.
private function add ($Id, $Text)
{
$sql="INSERT INTO htranslations (keyword, en, page, last) VALUES ('$Id', '$Text', '".$_SERVER['PHP_SELF']."', now())";
mysql_query($sql);
}
private function retrieve ( $Id )
{
$table = is_int ($Id) ? "translations" : "htranslations"; // A small tweak to support the two tables, but they should be merged.
$results = mysql_query ("SELECT ".mysql_real_escape_string($this->Lang)." FROM ".$table." WHERE keyword='".mysql_real_escape_string($Id)."'");
$row = mysql_fetch_assoc ($results);
return mysql_num_rows ($results) ? stripslashes ($row[$this->Lang]) : null;
}
// If needed to insert a name, for example, pass it in the $Arg
public function text($Id, $Arg = null)
{
$Text = $this->retrieve($Id);
if (empty($Text))
{
$Text = str_replace("_", " ", $Id); // If not found, replace all "_" with " " from the input string.
$this->add($Id, $Text);
}
return str_replace("%s", $Arg, $Text); // Not likely to have more than 2 variables into a single string.
}
}
How would you accomplish this in a proper yet simple (for coding) way? Are any of the proposed methods valid or can you come with a better one?
If the problem is simply that
$Translate->text('String_keyword');
feels to long, then consider making the Translate object into a Functor by implementing __invoke:
class Translate
{
// all your PHP code you already have
public function __invoke($keyword, $Arg = null)
{
return $this->text($keyword, $Arg)
}
}
You can then instantiate the object regularly with all the required dependencies and settings and call it:
$_ = new Translate(/* whatever it needs */);
echo $_('Hallo Welt');
That would not introduce the same amount of coupling and fiddling with the global scope as you currently consider to introduce through a wrapper function or as the Registry/Singleton solution suggested elsewhere. The only drawback is the non-speaking naming of the object variable as $_().
I would use a registry or make Translate a singleton. When i first initalize it i would pass in the language which would be dont in the bootstrap phase of the request. Then i would add methods to change the language later if necessary.
After doing that your function becomes pretty simple:
// singleton version
function _($id, $arg = null) {
return Translate::getInstance()->text($id, $arg);
}
// registry version
function _($id, $arg = null) {
return Registry::get('Translate')->text($id, $arg);
}
And then in your bootstap phase you would do something like:
$lang = get_user_lang(); // replace with however you do this
//registry version
Registry::set('Tranlaste', new Translate($lang));
// or the singleton version
// youd use create instance instead of getInstance
// so you can manage the case where you try to call
// getInstance before a language is set
Translate::createInstance($lang);

Is it a bad practice to define class options through an array?

When we take a look at Javascript frameworks like Dojo, Mootools, jQuery, JS Prototype, etc. we see that options are often defined through an array like this:
dosomething('mainsetting',{duration:3,allowothers:true,astring:'hello'});
Is it a bad practice to implement the same idea when writing a PHP class?
An example:
class Hello {
private $message = '';
private $person = '';
public function __construct($options) {
if(isset($options['message'])) $this->message = $message;
if(isset($options['person'])) $this->person = $person;
}
public function talk() {
echo $this->person . ' says: ' . $this->message;
}
}
The regular approach:
class Hello {
private $message = '';
private $person = '';
public function __construct() {}
public function setmessage($message) {
$this->message = $message;
}
public function setperson($person) {
$this->person = $person;
}
public function talk() {
echo $this->person . ' says: ' . $this->message;
}
}
The advantage in the first example is that you can pass as much options as you want and the class will only extract those that it needs.
For example, this could be handy when extracting options from a JSON file:
$options = json_decode($options);
$hello = new Hello($options);
This is how I do this regulary:
$options = json_decode($options);
$hello = new Hello();
if(isset($options['message'])) $hello->setmessage($options['message']);
if(isset($options['person'])) $hello->setperson($options['person']);
Is there a name for this pattern and do you think this is a bad practice?
I have left validation etc. in the examples to keep it simple.
There are good and bad aspects.
The good:
No need for multiple method signatures (i.e. overloading, where supported)
In keeping with the previous point: methods can be invoked with arguments in any order
Arguments can be dynamically generated, without needing to specify each one that will be present (example: you dynamically create an array of arguments based on user input and pass it to the function)
No need for "boilerplate" methods like setName, setThis, setThat, etc., although you might still want to include them
Default values can be defined in the function body, instead of the signature (jQuery uses this pattern a lot. They frequently $.extend the options passed to a method with an array of default values. In your case, you would use array_merge())
The bad:
Unless you properly advertise every option, your class might be harder to use because few will know what options are supported
It's one more step to create an array of arguments when you know ahead of time which you will need to pass
It's not always obvious to the user that default values exist, unless documentation is provided or they have access to the source code
In my opinion, it's a great technique. My favorite aspect is that you don't need to provide overloaded methods with different signatures, and that the signature isn't set in stone.
There's nothing wrong with that approach, especially if you have a lot of parameters you need to pass to a constructor. This also allows you to set default values for them and array_merge() them inside a constructor (kinda like all jQuery plugins do)
protected $default_params = array(
'option1' => 'default_value'
);
public function __construct($params = array()) {
$this->params = array_merge($this->default_params, $params);
}
If you want live examples of this "pattern", check out symfony framework, they use it almost every where: here's an example of sfValidatorBase constructor
When you give the arguments names it's called "Named Notation" v.s. "Positional Notation" where the arguments must be in a specific order.
In PHP you can pass an "options" parameter to give the same effect as other languages (like Python) where you can use a genuine Named Notation. It is not a bad practice, but is often done where there is a good reason to do it (i.e. in your example or a case where there are lots of arguments and they do not all need to set in any particular order).
I don't know the name, but i really doubt it is a bad practice, since you usally use this when you wan't to declare a small o quick function or class property
If there are mandatory options, they should be in the constructor's parameter list. Then you add the optional options with default values.
public function __construc($mandatory1, $mandatory2, $optional1="value", $optional2="value") { }
If all of your options are optional, then it can be useful to create a constructor taking an array. It would be easier to create the object than with a "normal constructor" : you could provide just the options you want, while with a "normal constructor" if you want to provide $optional2, you have to provide $optional1 (even setting it to the default value).
I wouldn't say its bad practice, at least if you trust the source of the data.
Another possibility would be dynamically calling the setters according to the options array key, like the following:
public function __construct($options) {
foreach($options as $option => $value) {
$method = 'set'.$option;
if(method_exists($this, $method)
call_user_func(array($this, $method, $value);
}
}
Why not do both? Have your constructor cake and eat it too with a static factory "named constructor":
$newHello = Hello::createFromArray($options);
You first have your constructor with the options in order. Then add a static method like this to the same class:
public static function createFromArray($options){
$a = isset($options['a']) ? $options['a'] : NULL;
$b = isset($options['b']) ? $options['b'] : NULL;
$c = isset($options['c']) ? $options['c'] : NULL;
return new Hello($a, $b, $c);
}
This will keep new developers and IDE's happy as they can still see what it takes to construct your object.
I agree with the general attitude of the answers here in that either way is a viable solution depending on your needs and which is more beneficial for your app.

Categories