cakephp魔术查找功能与通用字段

I want this

$this->$model_name->findByProgram_id($id)

to be executed something like this

$this->$model_name->findBy.$field_id($id) 

where $field_id = 'program_id and $id is the actual field value. This is required for dynamically calling function. Will it be possible or i should process with hard-coded field name.

Hope this will make sense!!! thanks inn advance

You can do it by using the inflector class in Cakephp, this is a concept so may need fleshing out but...

$methodName = 'findBy' . Inflector:camelize($field_id);

$this->$model_name->$methodName($id);

Although the findByXXX magic methods can be convenient at times, they are not always the most efficient way to retrieve your data.

By no means I will tell you to completely avoid using them, but there are a few things to consider when using them;

1. They are 'magic' methods

Magic methods rely on a magic __call() method, which (if exists) is called by PHP. I'm not going to start another debate here, as several already exist. Magic methods does serve a purpose and can be used in many situations.

Just be aware that;

  • they cause (some) overhead (I think this can be ignored in most cases)
  • your IDE wont recognise them without additional PhpDoc
  • it may be less clear for other programmers, because without knowing they're magic, they won't be able to find the declaration of the method called (i.e. findById() does not exist in your source code)

2. They don't offer all the functionality that find(xxx) offers

CakePHP models may have a lot of 'relations' defined. Although simply calling Model->find('all') will give you all the data you need, in most cases it will give you more than you need. Model->find('all') in CakePHP is the equivalent of this query in SQL;

SELECT * FROM tableA JOIN tableB JOIN tableC.....

In other words; it will fetch all related fields and records of tableA, tableB and tableC.

In most cases you don't need all that data, and queries like this are inefficient, use a lot of memory, and cause your website to perform poorly.

CakePHP has a recursive option, that allows you to specify how deep to retrieve your data, e.g. By setting recursive = -1, CakePHP will only retrieve data from the 'main' table, and not related tables;

SELECT * FROM tableA;

However, if you want to retrieve data from tableA and tableC but not from tableB, you're out of luck when using recursive

Getting Finer control

If you want to have finer control, use the Containable behavior. This behavior allows you to specify exactly which relations should be included in your results, for example;

$this->ModelA->find('all', array(
    'fields' => array(
        'ModelA.id',
        'ModelA.name',
        'ModelC.name',
    ),
    'contain' => array(
        'ModelC',
    )
));

As long as you're not specifying a contain key in your find options, the ContainableBehavior will not do anything, so it's save to add this behavior by default by adding it to the $actsAs array of your AppModel

Be specific

Whether you're using the ContainableBehavior or not, being specific is always good practice. Always check the SQL queries that are executed by CakePHP to check if they make sense. Always check the data that is returned by those queries; check if they don't contain data that you won't be using.

  • Set recursive to -1 by default in your AppModel (public $recursive = -1;). Only set recursive to 1 or 2 if you actually need it. If so, do this on a per query basis.
  • Use the Containable behavior; it allows you much finer control over what will be retrieved by your queries.
  • Always specify exactly the fields that you need. Preferable prefix the fields with the Model-alias to prevent errors if multiple tables in the relation contain fields with the same name (e.g. use 'ModelA.name' instead of just 'name')

Move data-related logic to your Model - putting it all together

Try to move all data-related logic/code out of your controller and move it to your Models.

To get back to your original question; create your own 'convenience' method that allows you to search your model by any field, for example; to enable searching your User Model and retrieve only the id, name and department-name

User extends AppModel
{
    public $actsAs = array('Containable');

    public function searchByField($field, $value)
    {
        return $this->find('all',
            array(
                'fields' => array(
                    'User.id',
                    'User.name',
                    'Department.name',
                ),
                'conditions' => array(
                    // this will dynamically search by $field
                    $field => $value,
                ),
                'contain' => array(
                    'Department',
                )
            )
        );

    }
}

This may seem like a lot of code, however, if you become more experienced with CakePHP, methods like this are written in less than 5 minutes. On the plus side; they will give you full control over what will be retrieved and can easily be modified to fit your needs.

In your controller, the amount of code is just one line; use it like this;

// Search by email
$data = $this->User->searchByField('User.email', 'foo@example.com');

// Search by name
$data = $this->User->searchByField('User.name', 'admin');