使用php call_user_func减少代码中的冗余

时间:2016-07-28 14:48:53

标签: rest laravel controller

我正在使用Laravel 5.2来创建REST API。我有50多个资源需要以JSON格式返回,因此对于我的每个资源,控制器看起来像这样:

public function index(Request $request)
{
    $holdingAccounts = HldAcc::all();

    return response()->json(['HldAccs' => $holdingAccounts]);
}

然后还有一些其他类似的功能,如存储,创建,销毁,更新等。

所有其他控制器中的功能完全相同,不同之处在于资源名称。

所以我提出了使用call_user_func PHP方法的想法。所以我创建了一个名为ResourceController的父类,并且我的所有资源都扩展了它,而不是直接扩展Controller类。在Controller类中,我执行以下操作:

class ResourceController extends Controller
{
        private $className;

        public function setClassName($className)
        {
            $this->className = 'App\Models\\' . $className;
        }

        public function index(Request $request)
        {
            $resource = call_user_func(array($this->className, 'all'));

            return response()->json($resource);
        } 

// and the rest of the methods go similarly...

从我的HldAcc中我做到了:

public function index(Request $request)
{
    parent::setClassName('HldAcc');
    return parent::index($request);
}

这可能听起来不像代码减少,但是使用其他方法和其他资源可以节省很多。

现在我的问题是:我到处都听到call_user_func()表现不佳。真的吗?如果是这样,你们建议我做什么呢?

3 个答案:

答案 0 :(得分:0)

您不必将类名作为字符串,因为您可以获得laravel IoC的全部功能。

我建议你做的是创建一个基本控制器,在任何模型上提供你想要的方法:

class BaseEntityController {
    private $model;
    private $jsonName;
    public function __construct(Model $model, $jsonName) {
        $this->model = $model;
        $this->jsonName = $jsonName;
    }

    public function index(Request $request) {
         $data = $this->model->all();
         return response()->json([$this->jsonName => $data ]);
    }

    /* Many other functions that do fun things */
}

然后,您所要做的就是创建此控制器的子类,并使用laravel IoC使用typehinting注入正确的模型。

你的其他控制器将像

一样苗条
class HldAccController extends BaseEntityController {
    public function __construct(HldAcc $model) {
        parent::__construct($model, 'HldAcc');
    }
}

Laravel将创建HldAccController,自动注入HldAcc模型,它将为BaseEntityController提供正确的all()实现来完成他的工作。

另外,为了回答您的具体问题,使用call_user_func的性能损失可以忽略不计。它不是有史以来最优化的代码。但是我认为与laravel的启动或数据库调用相比,它不会成为瓶颈。

答案 1 :(得分:0)

@ Atrakeur的答案或使用数据库外观并为表名传递变量字符串将通过创建父控制器来实现您的目标。

但是,我想建议一种替代方法。你正在做的只是在每个资源上调用::all()方法。我想你可能想要考虑这样做的优点:

您可以这样定义路线:

Route::get('/api/search/{entity}', 'SearchController@getAll');

然后,在您的控制器中,您的index方法将是:

public function getAll($entity)
{
    return DB::table($entity)->all();
}

这可能是它..但是,如果您不希望每个资源都可访问,或者您希望能够通过URL而不是您的表名传递更易读的字符串,那该怎么办?那么我认为你的控制器看起来像这样:

protected $permittedResources = [
    'hold-account' => 'hld_acc'
];

public function getAll($entity)
{
    if (isset($this->permittedResources[$entity])) {
        return DB::table($this->permittedResources[$entity])->all();
    }

    // otherwise throw some exception or redirect someplace fun
}

使用这种方法,您可以使用单个小型控制器,同时具有可读URL,一种选择允许哪些资源的方法,一种选择在不允许资源时发生的情况的方法,以及一种将控制器扩展为包含的简单方法例如,您可能需要的其他类型的搜索。

PS正如其他人所说,使用call_user_func并不是什么大不了的事。

答案 2 :(得分:0)

我相信这样做@ Atrakeur的方式是最好的方式。但是在我看到答案之前已经解决过了。所以这是我的解决方案:

我创建了一个名为<resourcename>_controller的基本控制器类,并让所有资源控制器扩展它。

要让这个类运行,我必须更改控制器名称的命名约定;称他们为<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; class Resource_Controller extends Controller { public function index(Request $request) { // the get_class($this) method returns the child class name that was called // the str_replace method removes the 'App\Http\Controllers\' prefix leaving oly the class name $childClassName = str_replace('App\Http\Controllers\\', '', get_class($this)); // the explode method slices the string using the '_' as a delimiter and returns an array with two or more substrings // for example Ctlg_Controller is returned as ['Post', 'Controller'] $word = explode('_', $childClassName); // class names will either contain 2 parts or 3 parts // for example 'Post_Controller' or 'Post_Comment_Controller' $wordCount = count($word); // in case the class name is something like 'Post_Controller' if ($wordCount == 2) { // get the complete model class name. for example: 'App\Models\Post' $resourceName = 'App\Models\\' . $word[0]; // remove the 'App\Models\' at the beginning and an 's' at the end. // to get something like 'Posts' $shortResourceName = str_replace('App\Models\\', '', $resourceName) . 's'; // use the resourceName to query all rows in the database. this should evaluate to something // like App\Models\Ctlg::all() $resource = $resourceName::all(); } // in case the class name is something like 'Post_Comment_Controller' elseif ($wordCount == 3) { // get the complete first model class name. for example: 'App\Models\Post' $parentResourceName = 'App\Models\\' . $word[0]; // remove the 'App\Models\' at the beginning. to get something like 'Post' $shortParentResourceName = str_replace('App\Models\\', '', $parentResourceName); // get the complete second model class name. for example: 'App\Models\Comment' $resourceName = 'App\Models\\' . $word[1]; // remove the 'App\Models\' at the beginning. // and adds an 's' at the end // to get something like 'Comments' $shortResourceName = str_replace('App\Models\\', '', $resourceName) . 's'; // find a record in table $parentResourceName (for example 'Post') // using id provided in the URL {$shortParentResourceName} // the final statement should evaluate to something like // like App\Models\Post::find($request->Post) $parentResource = $parentResourceName::find($request->{$shortParentResourceName}); // should evaluate to something like: $parentResource->Comments; $resource = $parentResource->{$shortResourceName}; } return response()->json([$shortResourceName => $resource]); } } ,因为,解决方案取决于控制器的名称。

不用多说,这里是代码:

while (fetch){
$x = value; // multi value, like A, B, C, D, E for example
global $x;
echo $x; // echo print still ok

while (fetch){
echo "<select>". // i use select attribute in this case, it's plus a (.) dot
// so code inside this sub while actually working but $x can't be printed
echo $x; // not print anything
echo "<option" . (($x==$throw[0]) ? 'selected="selected"': '') ."value=".$throw[0].">".$throw[0]."</option>'";
// i use this option attribute, and want $x to be inside the option attribute basically, because it needs to be compared
"</select>";

} // end sub while

} // end while

我仍然没有实现剩余的REST功能,如商店,更新等......不确定我是否会继续使用这种方式,但如果我这样做,我将用以下内容更新此答案:我做的任何改变。