Reputation: 3024
I am getting a list of users to display on a page using the following code in members.php
:
$users = $user->getUsers($_GET['skill'], $_GET['hometowm'], $_GET['county']);
members.php
displays all membersmembers.php?skill=Foo
displays users with that skillmembers.php?hometown=Foo
displays users of hometown "Foo"members.php?county=Foo
display users of county (AKA state in US)members.php?skill=Foo&hometown=Foo
displays users of skill and hometownmembers.php?skill=Foo&county=Foo
displays users of skill and countyHere is what I'd like to know: Is there a way I can shorten the amount of if
statements or make them more efficient? Am I doing it correctly? Because I don't like the fact I have so many parameters especially when I want to expand.
**User.class.php**
public function getUsers($skill = null, $hometown = null, $county = null) {
$user_table = $this->cfg['users_table']['table'];
# Search Skill
if ($skill != null && $hometown == null && $county == null) {
$sql = 'SELECT skills.Title, users_skills.SkillId, users.*
FROM users INNER JOIN (skills INNER JOIN users_skills ON skills.SkillId = users_skills.SkillId) ON users.UserId = users_skills.UserId
WHERE (((skills.Title)="' . $skill . '"))';
# Search Hometown
} else if ($hometown != null && $skill == null && $county == null) {
$sql = 'SELECT * FROM users WHERE HomeTown = "' . $hometown . '"';
# Search County
} else if ($county != null && $skill == null && $hometown == null) {
$sql = 'SELECT * FROM users WHERE county = "' . $county . '"';
# Search Skill and Hometown
} else if ($skill != null && $hometown != null && $county == null) {
//sql
# Search Skill and County
} else if($skill != null && $county != null && $hometown == null){
} else {
$sql = "SELECT * FROM " . $user_table;
}
$stmt = $this->db->prepare($sql);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
Thanks.
Upvotes: 1
Views: 339
Reputation: 11813
There are actually two issues here. The first is that your interface design is too specific. The other is that you're mixing the data model with the database access layer, and that's making your code more complicated.
I've skipped some details in my examples for brevity (and the code is untested), but I presume you can fill in the gaps.
The first order of business is to make your interface more general. Instead of using one method that does everything, split county
, skill
, etc. into different methods:
class userFilter
{
protected $db;
protected $params;
protected function __construct()
{
$this->db = new database_connection();
}
public static function create()
{
return new userFilter();
}
public function addCounty($county)
{
// For simplicity, 'county' is the column name
$this->params['county'][] = $county;
// Return $this to enable method chaining, used below
return $this;
}
public function addSkill($skill)
{
$this->params['skill'][] = $skill;
return $this;
}
public function addHometown($hometown)
{
return $this;
}
public function fetchUsers()
{
return '$data';
}
}
With the above interface, you can optionally add any number of parameters, and each (potentially) has its own logic associated with validating input, writing SQL conditions, etc.
The hard part of this solution is writing the actual SQL. For that, you need to know a few things:
SELECT
clause)?WHERE
clause)?The easy way to solve this problems is by using arrays, because you can easily foreach
over them to include whichever may be specified, and you can use their key=>value pairing to maintain associations with respect to your database schema.
Now when you go to write the query for fetchUsers()
you can iterate over $this->params
using array_keys($this->params)
as the column names and $this->params
as the data. That might look something like this:
// Select all columns present in $this->params
$sql = 'SELECT id, '.implode(', ', array_keys($this->params));
$sql .= 'FROM table_name WHERE ';
$where = array()
foreach($this->params as $column => $ids){
$where[] = $column . ' IN ('.implode(', ', $ids).')';
}
$sql .= implode(' AND ', $where);
With a more complicated schema that requires joins, you may need a switch to handle each join and join condition, or you may find a way to cram that into an array. You'll also need extra logic to make sure you don't add an empty WHERE
clause or other silly things, but the meat of it is there.
When the above code is self-contained in a class, fetching data is very simple.
$results = userFilter::create()
->addCounty($county)
->addHometown($hometown)
->addSkill($skill)
->addSkill($skill)
->addSkill($skill)
->fetchUsers();
If you want to conditionally use certain methods:
$userFilter = userFilter::create();
if(isset($_GET['hometown'])){
$userFilter->addHometown($_GET['hometown']);
}
if(isset($_GET['skill'])){
$userFilter->addSkill($_GET['skill']);
}
$userFilter->fetchUsers();
Upvotes: 2
Reputation: 133662
I often end up with a construction about like this (approximately):
$select = array("users.*"); // columns
$where = array("1=1"); // WHERE clauses
$values = array(); // values to bind
$join = array(); // additional joins
if ($skill) {
$join[] = " JOIN users_skills USING (userid) JOIN skills USING (skillid)";
$where[] = "skills.title = ?";
$values[] = $skill;
}
if ($hometown) {
$where[] = "hometown = ?";
$values[] = $hometown;
}
if ($county) {
$where[] = "county = ?";
$values[] = $county;
}
$parenthesise = function($value) { return "($value)"; };
$sql = "SELECT ".implode(",", $select)
." FROM users ".implode("", $join)
." WHERE ".implode(" AND ", array_map($parenthesise, $where));
// now prepare $sql and execute with $values
Upvotes: 1
Reputation:
Doing your logic in an actual query or stored procedure will give you the optimizations provided by the RDBMS and will return fewer records, consuming less resources and making you do less work.
Upvotes: 0