开发者

Can I make this search function more efficient?

I am getting a list of users to display on a page usi开发者_如何学Pythonng the following code in members.php:

$users = $user->getUsers($_GET['skill'], $_GET['hometowm'], $_GET['county']);
  • members.php displays all members
  • members.php?skill=Foo displays users with that skill
  • members.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 hometown
  • members.php?skill=Foo&county=Foo displays users of skill and county

Here 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.


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.

Generalize the Interface

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.

Database Access Layer

The hard part of this solution is writing the actual SQL. For that, you need to know a few things:

  1. Which columns do you want (the SELECT clause)?
  2. Which rows do you want (the WHERE clause)?
  3. What does your schema look like, or more specifically, which column names correspond to which parameters?
  4. Do you want to select a single parameter multiple times? (I've assumed multiple)

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.

Using the Interface

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();

Extra

  • More on method chaining / fluent interfaces
  • You may want to look into an ORM such as Doctrine or Propel


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜