Related
In the past I've worked with framework as Slim or CodeIgniter, both provide method such as getWhere(), this method return true or false if the array content passed to the getWhere was found on database table.
Actually I've created my own layer class that extends PDO functionality, my goal is create a method that take care to look for a specific database content based on the supplied parameters, currently I created this:
public function findRecordById($table, $where = null, $param = null)
{
$results = $this->select("SELECT * FROM $table WHERE $where",$param);
if(count($results) == 0)
{
return false;
}
return true;
}
for search a content I simply do:
if(!$this->db->findRecordById("table_name", "code = :key AND param2 = :code",
array("key" => $arr['key'], "code" => $arr['code']))){
echo "content not found";
}
now all working pretty well but I think that the call on the condition is a bit 'too long and impractical, I would like to optimize everything maybe going all the content into an array or something, but until now I have a precise idea. Some help?
I don't quite understand your question, but for the code provided I could tell that such a method should never belong to a DB wrapper, but to a CRUD class, which is a completely different story.
If you want to use such a method, it should be a method of a Model class, and used like this
$article = Article::find($id);
While for a database wrapper I would strongly suggest you to keep with raw SQL
$sql = "SELECT * FROM table_name WHERE code = :key AND param2 = :code";
$data = $this->db->query($sql, $arr)->fetchAll();
is the clean, tidy, and readable code which anyone will be able to read and understand.
As a bonus, you will be able to order the query results using ODER BY operator.
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.
This is a bit philosophical but I think many people encountered this problem. The goal is to access various (dynamically declared) properties in PHP and get rid of notices when they are not set.
Why not to __get?
That's good option if you can declare your own class, but not in case of stdClass, SimpleXML or similar. Extending them is not and option since you usually do not instantiate these classes directly, they are returned as a result of JSON/XML parsing.
Example:
$data = '{"name": "Pavel", "job": "programmer"}';
$object = json_decode($data);
We have simple stdClass object. The problems is obvious:
$b = $data->birthday;
The property is not defined and therefore a notice is raised:
PHP Notice: Undefined property: stdClass::$birthday
This can happen very often if you consider that you get that object from parsing some JSON. The naive solution is obvious:
$b = isset($data->birthday) ? $data->birthday : null;
However, one gets tired very soon when wrapping every accessor into this. Especially when chaining the objects, such as $data->people[0]->birthday->year. Check whether people is set. Check if the first element is set. Check if birthday is set. Check if year is set. I feel a bit overchecked...
Question:
Finally, my question is here.
What is the best approach to this issue? Silencing notices does not seem to be the best idea. And checking every property is difficult. I have seen some solutions such as Symfony property access but I think it is still too much boilerplate. Is there any simpler way? Either third party library, PHP setting, C extension, I don't care as far as it works... And what are the possible pitfalls?
If I understand correctly, you want to deal with 3rd party Objects, where you have no control, but your logic requires certain properties that may not be present on the Object. That means, the data you accepting are invalid (or should be declared invalid) for your logic. Then the burden of checking the validity goes into your validator. Which I hope you already have following best practices to deal with 3rd party data. :)
You can use your own validator or one by frameworks. A common way is to write a set of Rules that your data needs to obey in order to be valid.
Now inside your validator, whenever a rule is not obeyed, you throw an Exception describing the error and attaching Exception properties that carry the information you want to use. Later when you call your validator somewhere in your logic, you place it inside try {...} block and you catch() your Exceptions and deal with them, that is, write your special logic reserved for those exceptions. As general practice, if your logic becomes too large in a block, you want to "outsource" it as function.
Quoting the great book by Robert Martin "Clean Code", highly recommended for any developer:
The first rule of function is that they should be small. The second is that they should be smaller than that.
I understand your frustration dealing with eternal issets and see as cause of the problem here that each time you need to write a handler dealing with that technical issue of this or that property not present. That technical issue is of very low level in your abstraction hierarchy, and in order to handle it properly, you have to go all the way up your abstraction chain to reach a higher step that has a meaning for your logic. It is always hard to jump between different levels of abstraction, especially far apart. It is also what makes your code hard to maintain and is recommended to avoid.
Ideally your whole architecture is designed as a tree where Controllers sitting at its nodes only know about the edges going down from them.
For instance, coming back to your example, the question is -
Q - What is the meaning for your app of the situation that $data->birthday is missing?
The meaning will depend on what the current function throwing the Exception wants to achieve. That is a convenient place to handle your Exception.
Hope it helps :)
One solution (I don't know if it's the better solution, but one possible solution) is to create a function like this:
function from_obj(&$type,$default = "") {
return isset($type)? $type : $default;
}
then
$data = '{"name": "Pavel", "job": "programmer"}';
$object = json_decode($data);
$name = from_obj( $object->name , "unknown");
$job = from_obj( $object->job , "unknown");
$skill = from_obj( $object->skills[0] , "unknown");
$skills = from_obj( $object->skills , Array());
echo "Your name is $name. You are a $job and your main skill is $skill";
if(count($skills) > 0 ) {
echo "\n\nYour skills: " . implode(",",$skills);
}
I think it's convienent because you have at the top of your script what you want and what it should be (array, string, etc)
EDIT:
Another solution. You could create a Bridge class that extends ArrayObject:
class ObjectBridge extends ArrayObject{
private $obj;
public function __construct(&$obj) {
$this->obj = $obj;
}
public function __get($a) {
if(isset($this->obj->$a)) {
return $this->obj->$a;
}else {
// return an empty object in order to prevent errors with chain call
$tmp = new stdClass();
return new ObjectBridge($tmp);
}
}
public function __set($key,$value) {
$this->obj->$key = $value;
}
public function __call($method,$args) {
call_user_func_array(Array($this->obj,$method),$args);
}
public function __toString() {
return "";
}
}
$data = '{"name": "Pavel", "job": "programmer"}';
$object = json_decode($data);
$bridge = new ObjectBridge($object);
echo "My name is {$bridge->name}, I have " . count($bridge->skills). " skills and {$bridge->donald->duck->is->paperinik}<br/>";
// output: My name is Pavel, I have 0 skills and
// (no notice, no warning)
// we can set a property
$bridge->skills = Array('php','javascript');
// output: My name is Pavel, my main skill is php
echo "My name is {$bridge->name}, my main skill is {$bridge->skills[0]}<br/>";
// available also on original object
echo $object->skills[0]; // output: php
Personally I would prefer the first solution. It's more clear and more safe.
Data formats which have optional fields are quite difficult to deal with. They're problematic in particular if you have third parties accessing or providing the data, since there rarely is enough documentation to comprehensively cover all causes for the fields to appear or disappear. And of course, the permutations tend to be harder to test, because coders won't instinctively realize that the fields may be there.
That's a long way of saying that if you can avoid having optional fields in your data, the best approach to dealing with missing object properties in PHP is to not have any missing object properties...
If the data you're dealing with is not up to you, then I'd look into forcing default values on all fields, perhaps via a helper function or some sort of crazy variation of the prototype pattern. You could build a data template, which contains default values for all fields of the data, and merge that with the real data.
However, if you do that, are you failing, unless? (Which is another programming philosophy to take into heart.) I suppose one could make the case that providing safe default parameters satisfies data validation for any missing fields. But particularly when dealing with third party data, you should exercise high level of paranoia against any field you're plastering with default values. It's too easy to just set it to null and -- in the process -- fail to understand why it was missing in the first place.
You should also ask what are you trying to achieve? Clarity? Safety? Stability? Minimal code duplication? These are all valid goals. Being tired? Less so. It suggests a lack disciprine, and a good programmer is always disciprined. Of course, I'll accept that people are less likely to do something, if they view it as a chore.
My point is, the answer to your question may differ depending on why it's being asked. Zero effort solution is probably not going to be available, so if you're only exchanging one menial programming task to another one, are you solving anything?
If you are looking for a systematic solution that will guarantee that the data is always in the format you have specified, leading to reduced number of logical tests in the code that processes that data, then perhaps what I suggested above will be of help. But it will not come without a cost and effort.
in PHP version 8
you can use Nullsafe operator as follow:
$res = $data?->people[0]?->birthday?->year;
The best answers have been given, but here is a lazy one:
$data = '{"name": "Pavel", "job": "programmer"}';
$object = json_decode($data);
if(
//...check mandatory properties: !isset($object->...)&&
){
//error
}
error_reporting(E_ALL^E_NOTICE);//Yes you're right, not the best idea...
$b = $object->birthday?:'0000-00-00';//thanks Elvis (php>5.3)
//Notice that if your default value is "null", you can just do $b = $object->birthday;
//assign other vars here
error_reporting(E_ALL);
//Your code
Use a Proxy object - it will add just one tiny class and one line per object instantiation to use it.
class ProxyObj {
protected $obj;
public function __construct( $obj ) {
$this->_obj = $obj;
}
public function __get($key) {
if (isset($this->_obj->$key)) {
return $this->_obj->$key;
}
return null;
}
public function __set($key, $value) {
$this->_obj->$key = $value;
}
}
$proxy = new ProxyObj(json_decode($data));
$b = $proxy->birthday;
You can decode the JSON object to an array:
$data = '{"name": "Pavel", "job": "programmer"}';
$jsonarray = json_decode($data, true);
$b = $jsonarray["birthday"]; // NULL
function check($temp=null) {
if(isset($temp))
return $temp;
else
return null;
}
$b = check($data->birthday);
I've hit this problem, mainly from getting json data from a nosql backed api that by design has inconsistent structures, eg if a user has an address you'll get $user->address otherwise the address key just isn't there. Rather than put tons of issets in my templates I wrote this class...
class GracefulData
{
private $_path;
public function __construct($d=null,$p='')
{
$this->_path=$p;
if($d){
foreach(get_object_vars($d) as $property => $value) {
if(is_object($d->$property)){
$this->$property = new GracefulData($d->$property,$this->_path . '->' . $property);
}else{
$this->$property = $value;
}
}
}
}
public function __get($property) {
return new GracefulData(null,$this->_path . '->' . $property);
}
public function __toString() {
Log::info('GracefulData: Invalid property accessed' . $this->_path);
return '';
}
}
and then instantiate it like so
$user = new GracefulData($response->body);
It will gracefully handle nested calls to existing and non existing properties. What it can't handle though is if you access a child of an existing non-object property eg
$user->firstName->something
Lots of good answers here, I consider #Luca 's answer as one of the best - I extended his a little so that I could pass in either an array or object and have it create an easy to use object. Here's mine:
<?php
namespace App\Libraries;
use ArrayObject;
use stdClass;
class SoftObject extends ArrayObject{
private $obj;
public function __construct($data) {
if(is_object($data)){
$this->obj = $data;
}elseif(is_array($data)){
// turn it into a multidimensional object
$this->obj = json_decode(json_encode($data), false);
}
}
public function __get($a) {
if(isset($this->obj->$a)) {
return $this->obj->$a;
}else {
// return an empty object in order to prevent errors with chain call
$tmp = new stdClass();
return new SoftObject($tmp);
}
}
public function __set($key, $value) {
$this->obj->$key = $value;
}
public function __call($method, $args) {
call_user_func_array(Array($this->obj,$method),$args);
}
public function __toString() {
return "";
}
}
// attributions: https://stackoverflow.com/questions/18361594/how-to-solve-the-missing-object-properties-in-php | Luca Rainone
I have written a helper function for multilevel chaining, for example, let's say you want to do something like $obj1->obj2->obj3->obj4, and my helper will return empty string if one of the tiers is not defined or null
class MyUtils
{
// for $obj1->obj2->obj3: MyUtils::nested($obj1, 'obj2', 'obj3')
// returns '' if some of tiers is null
public static function nested($obj1, ...$tiers)
{
if (!isset($obj1)) return '';
$a = $obj1;
for($i = 0; $i < count($tiers); $i++){
if (isset($a->{$tiers[$i]})) {
$a = $a->{$tiers[$i]};
} else {
return '';
}
}
return $a;
}
}
I'm using Doctrine with Symfony in a couple of web app projects.
I've optimised many of the queries in these projects to select just the fields needed from the database. But over time new features have been added and - in a couple of cases - additional fields are used in the code, causing the Doctrine lazy loader to re-query the database and driving the number of queries on some pages from 3 to 100+
So I need to update the original query to include all of the required fields. However, there doesn't seem an easy way for Doctrine to log which field causes the additional query to be issued - so it becomes a painstaking job to sift through the code looking for usage of fields which aren't in the original query.
Is there a way to have Doctrine log when a getter accesses a field that hasn't been hydrated?
I have not had this issue, but just looked at Doctrine_Record class. Have you tried adding some debug output to the _get() method? I think this part is where you should look for a solution:
if (array_key_exists($fieldName, $this->_data)) {
// check if the value is the Doctrine_Null object located in self::$_null)
if ($this->_data[$fieldName] === self::$_null && $load) {
$this->load();
}
Just turn on SQL logging and you can deduce the guilty one from alias names. For how to do it in Doctrine 1.2 see this post.
Basically: create a class which extends Doctrine_EventListener:
class QueryDebuggerListener extends Doctrine_EventListener
{
protected $queries;
public function preStmtExecute(Doctrine_Event $event)
{
$query = $event->getQuery();
$params = $event->getParams();
//the below makes some naive assumptions about the queries being logged
while (sizeof($params) > 0) {
$param = array_shift($params);
if (!is_numeric($param)) {
$param = sprintf("'%s'", $param);
}
$query = substr_replace($query, $param, strpos($query, '?'), 1);
}
$this->queries[] = $query;
}
public function getQueries()
{
return $this->queries;
}
}
And add the event listener:
$c = Doctrine_Manager::connection($conn);
$queryDbg = new QueryDebuggerListener();
$c->addListener($queryDbg);
I made a class. I give it some objects (mostly retreived database rows) as input, and tell it which fields it has to show, and which buttons I want. Then it renders a very nice html table! It's pretty awesome, I think.
$ot = new ObjectTable();
$ot->objects = $some_objects;
$ot->fields = array('id','name','description','image');
$ot->buttons = array('edit','delete');
$ot->render();
However, I also want to be able to manipulate the data it shows. For example, i want to be able to truncate the 'description' field. Or to display an image thumbnail (instead of 'picture.jpg' as text). I don't know how to pass these functions to the class. Perhaps something like:
$ot->functions = array(null,null,'truncate','thumbnail');
But then I don't know how to convert these strings to run some actual code, or how to pass parameters.
There must be a nice way to do what I want. How?
Check this question and the answer is:
As of PHP5.3 you could use closures
or functors to pass methods
around. Prior to that, you could write
an anonymous function with
create_function(), but that is
rather awkward.
But what you are trying to achieve is best solved by passing Filter Objects to your renderer though. All filters should use the same method, so you can use it in a Strategy Pattern way, e.g. write an interface first:
interface Filter
{
public function filter($value);
}
Then write your filters implementing the interface
class TruncateFilter implements Filter
{
protected $_maxLength;
public function __construct($maxLength = 50)
{
$this->_maxLength = (int) $maxLength;
}
public function filter($value)
{
return substr(0, $this->_maxLength, $value) . '…';
}
}
Give your ObjectTable a method to accept filters
public function addFilter($field, Filter $filter)
{
if(in_array($field, $this->fields)) {
$this->_filters[$field][] = $filter;
}
return $this;
}
And when you do your ObjectTable instantiation, use
$ot = new ObjectTable();
$ot->objects = $some_objects;
$ot->fields = array('id','name','description','image');
$ot->addFilter('description', new TruncateFilter)
->addFilter('name', new TruncateFilter(10))
->addFilter('image', new ThumbnailFilter);
Then modify your render() method to check if there is any Filters set for the fields you are rendering and call the filter() method on them.
public function render()
{
foreach($this->fields as $field) {
$fieldValue = // get field value somehow
if(isset($this->filters[$field])) {
foreach($this->filters[$field] as $filter) {
$fieldValue = $filter->filter($fieldValue)
}
}
// render filtered value
}
}
This way you can add infinite filters.
PHP has a pseudo-type called "callback", which is actually an ugly closure in disguise. You can call such callbacks using call_user_func() or call_user_func_array():
$callback = 'strlen';
echo call_user_func($callback, '123');
// will output 3 (unless you're using a strange encoding)
You are looking for create_function().
However, creating functions on runtime and adding them to a class doesn't sound right to me. It's likely to become a maintenance nightmare very quickly. There are better ways to achieve what you want. What kind of manipulation would the functions to to the data? How about storing them in a "tools" class and connecting that with the table object when needed?
$functions = array(null,null,'truncate','thumbnail');
$function_1 = $functions[3];
$my_string = 'string to truncate';
$result = call_user_func($functions[2], $my_string);
If you want to pass multiple parameters, use call_user_func_array instead.
You might also want to explore call_user_func, which allows you to call a function based on a string representing its name.