How specific should your functions be in Zend_Model? - php

if someone could help me with a conceptual question it would be great: Suppose I have a model that deals with a table called Persons. Normally I would have a standard fetch function like this:
public function fetchPersonById($person_id)
{
$result = 0;
if ((int)$person_id > 0) {
$select = $this->select()
->from($this->_name, array('Id' => 'Person_Id',
'Name' => 'Person_Name',
'Age' => 'Person_Age',
'Sex' => 'Person_Sex'));
->where('Person_Id = ?', $person_id);
$result = $this->fetchRow($select);
}
return $result;
}
Now suppose for some reason I need to fetch a person's Sex by it's Name.. and later on it's age by it's name. Would you them add different functions like:
public function fetchPersonSexByName($person_name)
{
// ...
->from($this->_name, array('Sex' => 'Person_Sex');
->where('Person_Name = ?', $person_name); ...
// ...
}
and so on... After a while you could see yourself with thousands of short methods like this.. Are you guys that specific or you wether pull the whole record (fetchall) and than later in the code just keep the column you want to use? On this case wouldn't you be breaking the whole MVC because if I want to get
someone's Sex my model (or whoeve is calling the function) would need to know the columns name in the database?
I also tought about doing something more generic like
public function $this->fetchColumnA_By_ColumnB_ColumnBValue($columnA_name,
$columnB_name, $columnA_name)
{
//...
}
And than have my short methods to be calling this more flexible column. So that I would have something like:
public function fetchPersonSexByName($person_name)
{
//...
$this->fetchColumnA_By_ColumnB_ColumnBValue('sex', 'name', 'martin');
}
Anyway.. How do you guys approach this probably common issue?

I would tend to do a version of your last generic example. The generic method would be protected (or even private) and your more specific (public) methods would call this. To avoid code repetition.
However, I'm not sure how generic I would go. May be just...
protected function _fetchColumnById($id, $column) {...}
protected function _fetchColumnByName($name, $column) {...}
...but this would depend on the requirements.
you could see yourself with thousands of short methods
If you think you'll get to 1000's of requests, then it might be better to read the whole record(s) and cache this somehow?

What you could do is make a magic __call function in your model or in his parent.
If some method doesn't exist it will go thru that magic function. Something like:
class Model_Test {
public function __call($method, $args) {
if(preg_match('/fetch([a-zA-Z]+)by([a-zA-Z]+)/i', $method, $result)) {
$fetch = $result[1];
$column = $result[2];
echo 'SELECT ' . $fetch . ' FROM test WHERE ' . $column . ' = "' . (string)$args[0] . '"';
//build your query here and make sure you make it secure with bind param, etc.
} else {
//call parent __call? Or throw an error?
}
}
}
$model = new Model_test();
$model->fetchSexByName('martin');
Just a quick example, offcourse you need to work it out. Success!

Related

How to refactor this setTags method?

I want to follows a rule one function do one thing only. So i need to refactor this method. That do many thing to do a task (setTags). Mostly inside foreach the code will re-use to other method (setGenres, setLanguage, setCharacter, setCountries) with same style code.
This raw original method. in bottom my refactor method.
public function setTags($tags)
{
foreach ($tags as $name)
{
$tag = Tag::firstOrCreate([
'name' => ucwords($name)
]);
if (empty($this->media->tags()->where('tag_id', $tag->id)->exists())) {
$this->media->tags()->attach($tag->id);
}
}
}
Refactor method
// is it right to name it createModelValue?
public function createModelValue($model, $value) {
$collection = $model::firstOrCreate([
'name' => ucwords($value)
]);
if (!empty($collection)) {
return $collection;
}else{ return NULL; }
}
// new problem how to pass $model (tag) as function relationship to replace tags function
public function setModel($model) {
if (empty($this->media->tags()->where("{$model}_id", $model->id)->exists())) {
$this->media->tags()->attach($model->id);
}
}
Laravel has a sync() method that will come in handy here, instead of attaching each tag individually, you can just say attach all tags with following id and remove everything else.
$tagsCreated = collect($tags)->map(function (string $tag) {
return Tag::firstOrCreate([
'name' => ucwords($tag)
]);
});
$this->media->tags()->sync($tagsCreated);
In all honesty, being explicit in code is sometimes better than being clever. Your first version of your method is very specific and clearly indicates what it does. Your refactored clever version, i was like "uh whats going on". So choosing between these two, i would like the one above. Also your method only does one, it set tags from an array of strings. Could that be optimized yes, but very minor. The principle is more applicable if you have an email service that sends emails, you should not start to create pdf's at the same time.
I have done similar logic as the refactored but consider how big the project is and how many times you repeated the logic before you start that.
A generic refactor could be as simple as this, if you are feeling adventurous you could implement tags as a trait and/or an interface.
function setTags($model, $tags) {
$tagsCreated = collect($tags)->map(function (string $tag) {
return Tag::firstOrCreate([
'name' => ucwords($tag)
]);
});
$model->tags()->sync($tagsCreated);
}

How to give meaningful names to functions in PHP?

This might seem like a stupid and trivial question. I am having problem naming functions in PHP. I have two functions that retrieves all the information of a student given its id or name and email.
Since PHP doesn't have function overloading in the same sense as JAVA, I am having difficulty naming the functions.
Here is what I have done. These are the names that I have given them.
get_students_with_id($id) and get_students_with_name_and_email($name, $email)
But the parameters are gonna increase. I need a better and simple solution to name these functions or methods. BTW, they all belong to the same class. So what am I gonna do? Thanks in advance.
In PHP there doesn't exist the concept of method overriding like in JAVA, for example, but you can send default parameters, for example:
get_students($id, $name = null, $email = null)
This means that you don't need to call the function with the three parameters. You can do it by calling it just with one and it will assume it is the id. For example, if you want to have a function working for your example above, you could do something like:
function get_students($id, $name = null, $email) {
if (!empty($id)) {
// Get students by their ids
} else if (!empty($name) && !empty($email)) {
// Get students by their names and emails
}
}
And you can call the function above:
get_students(1); //Will retrieve studen with id 1
get_students(null, "Name", "email#email.com"); //Will retrieve students with name "Name" and email "email#email.com"
A search method could look something like this:
class Student {
public static $columns = ['id', 'name', 'email', 'password', /* ... */];
// Imagine that this method is called with the following array:
// ['name' => 'Joe', 'password' => 'Pa55w0rD']
public static function search(array $queries) {
// We will be appending WHERE clauses to this SQL query
$sql = 'SELECT * FROM students WHERE ';
// Get the column names
$parameters = array_keys($queries);
// Create a parameterized WHERE clause for each column
foreach ($parameters as & $param) {
if ( ! in_array($param, self::$columns)) {
throw "Invalid column";
}
$param = "{$param} = :{$param}";
}
// Squish parameterized WHERE clauses into one
// and append it to the SQL query
$sql .= implode(' AND ', $parameters);
// The query will now look something like this:
// SELECT * FROM students WHERE name = :name AND password = :password
// Prepare the SQL query
$stmt = DB::instance()->prepare($sql);
// Go over the queries and bind the values to the columns
foreach ($queries as $col => $val) {
$stmt->bindValue(":" . $col, $val);
// Internally the query will look something like this:
// SELECT * FROM students WHERE name = 'Joe' AND password = 'Pa55w0rD'
}
// Execute
$result = $stmt->execute();
// ...
}
}
To use the method you would do something like this:
$student = Student::search([
'name' => 'Joe',
'password' => 'Pa55w0rD',
]);
You would want to handle the data in a safer way (making sure the password is hashed, for instance), but the general idea is there.
Why not use get_students($id=0, $name='', $email='') and so on for your other parameters, then have the function do whatever is necessary based on the passed parameters?
If that gets to be too much, pass an array check for keys. So if array('id' => 1) is passed then if (array_key_exists('id', $input)) {...} would catch it and proceed with actual function work, but if other keys/values are passed then a subsequent appropriate elseif would catch it.
Update: I think a format like this might be able to handle most of your use cases, based on some of the comments I read in the question. Not sure what your DB is, so this was done with MySQL in mind.
function get_students($input) {
$where = array();
$allowed_columns = array('id', 'name', 'email');
foreach ($allowed_columns as $key) {
if (!array_key_exists($key, $input)) continue;
$where[] = "`$key` = '" . mysqli_escape_string($input[$key]) . "'";
}
if ($where) {
$query = 'SELECT ... FROM `...` WHERE ' . join(' AND ', $where);
// etc...
} else {
return false;
}
}
I would use a class instead of multiple functions
class Student
{
public static function byName($name)
{
// ...
}
public static function byId($id)
{
// ...
}
}
$student = Student::byName('joe');
This would allow it to be much cleaner and more extendible, as you can put common logic in protected static methods in the class.
If you want to do multiples you can do some chaining which is a little more complicated.
I've mocked up a quick ideone which you can reverse engineer:
http://ideone.com/duafK4

Can an Abstract Class in PHP Have a Default Method?

I've most certainly got something very basic wrong here.
Here is the code that is part of my Abstract Class:
private $outarray = null;
public function add_to_array($ahref, $docname, $description) {
$row = array('ahref' => $ahref, 'docname' => $docname, 'description' => $description);
if (!isset($this->outarray)) {
$this->outarray = array();
}
array_push($this->outArray, $row);
}
When I step through the code, though, the outArray remains null. It is never created and never populated.
I'm still green with PHP, but this help doc seems to leave me believing that this is OK to do:
http://www.php.net/manual/en/language.oop5.abstract.php
...particularly where they are declaring the Common method printOut() that performs some action.
I've got 5 elements I am trying to populate outArray with, but each of the 5 times I circle into this function, I come out with outArray being NULL.
Variables are case sensitive. You have in one place $this->outarray and in array_push you have $this->outArray
Ugh.
PHP is case sensitive, but it does not complain about it because it was assuming I had another variable declared on the fly.
Correct way:
public function add_to_array($ahref, $docname, $description) {
$row = array('ahref' => $ahref, 'docname' => $docname, 'description' => $description);
if (!isset($this->outarray)) {
$this->outarray = array();
}
array_push($this->outarray, $row);
}

Why do we need the unset here?

Given the following example: (from Zend Quick Start Tutorial btw)
public function save(Application_Model_Guestbook $guestbook)
{
$data = array(
'email' => $guestbook->getEmail(),
'comment' => $guestbook->getComment(),
'created' => date('Y-m-d H:i:s'),
);
if (null === ($id = $guestbook->getId())) {
unset($data['id']);
$this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('id = ?' => $id));
}
}
Why do we need the unset there ?
I mean, why do we need to destroy a specific array key, if we haven't declare it before ?
And even more bizarre, where do we declare it anyway?
We can have a look on getDbTable method, but even looking at it, I don't find an answer:
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
And if we look into the setDbTable method, there's no $data anywhere.
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
I know that Zend Framework will automatically find the id of our table here:
class Application_Model_DbTable_Guestbook extends Zend_Db_Table_Abstract
{
/** Table name */
protected $_name = 'guestbook';
}
But I don't understand if this is related somehow...
I believe I've put all the relevant information. Still, if there's nothing relevant, perhaps I'm missing something here:
(source)
http://framework.zend.com/manual/en/learning.quickstart.create-model.html
Thanks a lot
The code seems to imply that $data['id'] is always set, but it might have an empty value ('' or '0'). The unset is there to prevent the INSERT SQL query from trying to insert every new record with a fixed id of 0 (or to prevent the query from breaking due to invalid SQL syntax, can't tell with just this information) in this case.
Update: After reading it once more, it's obvious that the array $data cannot have its id member set at all (there's no code that might set it). Therefore that line is completely redundant the way the code is written right now. It might be a leftover from a previous version of the code.
Probably id is an autoincrement field. The unset is used to make sure, that the INSERT statement will not use a random id, but null.

PHP: Class property chaining in variable variables

So, I have a object with structure similar to below, all of which are returned to me as stdClass objects
$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;
etc... (note these examples are not linked together).
Is it possible to use variable variables to call contact->phone as a direct property of $person?
For example:
$property = 'contact->phone';
echo $person->$property;
This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this.
Any ideas?
In response to answers relating to proxy methods:
And I would except this object is from a library and am using it to populate a new object with an array map as follows:
array(
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
);
and then foreaching through the map to populate the new object. I guess I could envole the mapper instead...
If i was you I would create a simple method ->property(); that returns $this->contact->phone
Is it possible to use variable variables to call contact->phone as a direct property of $person?
It's not possible to use expressions as variable variable names.
But you can always cheat:
class xyz {
function __get($name) {
if (strpos($name, "->")) {
foreach (explode("->", $name) as $name) {
$var = isset($var) ? $var->$name : $this->$name;
}
return $var;
}
else return $this->$name;
}
}
try this code
$property = $contact->phone;
echo $person->$property;
I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first.
For example:
$property = 'contact->phone';
echo $person->{$property};
The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly.
$xml->{a-disallowed-field}
If it is legal it does not mean it is also moral. And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. Take a look at the law of demeter:
Law of Demeter
try this if you really really want to:
json_decode(json_encode($person),true);
you will be able to parse it as an array not an object but it does your job for the getting not for the setting.
EDIT:
class Adapter {
public static function adapt($data,$type) {
$vars = get_class_vars($type);
if(class_exists($type)) {
$adaptedData = new $type();
} else {
print_R($data);
throw new Exception("Class ".$type." does not exist for data ".$data);
}
$vars = array_keys($vars);
foreach($vars as $v) {
if($v) {
if(is_object($data->$v)) {
// I store the $type inside the object
$adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
} else {
$adaptedData->$v = $data->$v;
}
}
}
return $adaptedData;
}
}
OOP is much about shielding the object's internals from the outside world. What you try to do here is provide a way to publicize the innards of the phone through the person interface. That's not nice.
If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures:
class conv {
static function phone( $person ) {
return $person->contact->phone;
}
}
// imagine getting a Person from db
$person = getpersonfromDB();
print conv::phone( $p );
If ever you need a more specialized function, you add it to the utilities. This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability.
Another way is to 'extend' the Person class with conveniences, built around the core class' innards:
class ConvPerson extends Person {
function __construct( $person ) {
Person::__construct( $person->contact, $person->name, ... );
}
function phone() { return $this->contact->phone; }
}
// imagine getting a Person from db
$person = getpersonfromDB();
$p=new ConvPerson( $person );
print $p->phone();
You could use type casting to change the object to an array.
$person = (array) $person;
echo $person['contact']['phone'];
In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures.
In the example above, person has contact and dob. The contact also contains address. Trying to access the data from the uppermost level is not uncommon when writing complex database applications. However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects.
As much as I hate saying it, you could do an eval :
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. Do remember that magic methods are somewhat slow though.
class Person {
private $fields = array();
//...
public function __get($name) {
if (empty($this->fields)) {
$this->fields = get_class_vars(__CLASS__);
}
//Cycle through properties and see if one of them contains requested field:
foreach ($this->fields as $propName => $default) {
if (is_object($this->$propName) && isset($this->$propName->$name)) {
return $this->$propName->$name;
}
}
return NULL;
//Or any other error handling
}
}
I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. Thank for you for your answers.
Edit:
If you are interested:
public function __construct($data)
{
$this->_raw = $data;
}
public function getContactPhone()
{
return $this->contact->phone;
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
}
if (isset($this->_raw->$name)) {
return $this->_raw->$name;
}
return null;
}
In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. You can then 'decorate' your objects with the same retrieval code.
An example of 'retrieval only' decoration code:
function retrieve( $obj, $path ) {
$element=$obj;
foreach( $path as $step ) {
$element=$element[$step];
}
return $element;
}
function decorate( $decos, &$object ) {
foreach( $decos as $name=>$path ) {
$object[$name]=retrieve($object,$path);
}
}
$o=array(
"id"=>array("name"=>"Ben","surname"=>"Taylor"),
"contact"=>array( "phone"=>"0101010" )
);
$decorations=array(
"phone"=>array("contact","phone"),
"name"=>array("id","name")
);
// this is where the action is
decorate( $decorations, &$o);
print $o->name;
print $o->phone;
(find it on codepad)
If you know the two function's names, could you do this? (not tested)
$a = [
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
];
foreach ($a as $name => $chain) {
$std = new stdClass();
list($f1, $f2) = explode('->', $chain);
echo $std->{$f1}()->{$f2}(); // This works
}
If it's not always two functions, you could hack it more to make it work. Point is, you can call chained functions using variable variables, as long as you use the bracket format.
Simplest and cleanest way I know of.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Usage
echo getValueByPath($person,'contact->email');
// Returns the value of that object path

Categories