验证输入和查询优化的问题

时间:2017-01-29 17:09:20

标签: php mysql database laravel

我正在创建一个管理系统,教师可以管理学生的最终项目,而前者可以看到其他学生创建的内容

我是一个laravel新手,我在优化查询和验证网址时遇到了问题

这是我的表格模式:

Cursos

+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| curso | varchar(255)     | NO   |     | NULL    |                |
+-------+------------------+------+-----+---------+----------------+

Trienios

+--------------+------------------+------+-----+---------+----------------+
| Field        | Type             | Null | Key | Default | Extra          |
+--------------+------------------+------+-----+---------+----------------+
| id           | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| data_trienio | varchar(255)     | NO   |     | NULL    |                |
| curso_id     | int(11)          | NO   |     | NULL    |                |
| oe_id        | int(11)          | NO   |     | NULL    |                |
+--------------+------------------+------+-----+---------+----------------+

Alunos

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| id_cartao  | int(10) unsigned | NO   | UNI | NULL    |                |
| nome       | varchar(255)     | NO   |     | NULL    |                |
| email      | varchar(255)     | NO   | UNI | NULL    |                |
| trienio_id | int(11)          | NO   |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

PAP

+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| nome      | varchar(255)     | NO   |     | NULL    |                |
| descricao | text             | NO   |     | NULL    |                |
| nota      | int(11)          | NO   |     | NULL    |                |
| aluno_id  | int(11)          | NO   |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+

到目前为止,我已经设法根据cursos和trienios表中定义的记录设置动态网址,如下所示:http://localhost:8000/TGEI/2014-2017

(TGEI是cursos表中的记录,用于获取相关的trienio记录,而2014-2017是trienios表中的记录,该记录与1对多关系中的curso记录相关并获取相关的pap记录)

这一切都很顺利,但我在优化效率极低的查询方面遇到了麻烦,这些问题在数据库增长时将成为一个非常重要的问题

这是我的关系:

Curso.php

public function trienio()
{
    return $this->hasMany('App\Trienio');
}

Trienio.php

public function curso()
{
    return $this->belongsTo('App\Curso');
}

public function oe()
{
    return $this->belongsTo('App\OE');
}

public function aluno()
{
    return $this->hasMany('App\Aluno');
}

Aluno.php

public function trienio()
{
    return $this->belongsTo('App\Trienio');
}

public function pap()
{
    return $this->hasOne('App\PAP');
}

PAP.php

protected $table = 'pap';

public function aluno()
{
    return $this->belongsTo('App\Aluno');
}

这些是负责提供用户可访问页面的控制器:

CursoController.php

public function index(Curso $curso)
{
    $cursos = $curso->all();

    return view('curso')->withCursos($cursos);
}

TrienioController.php

public function index(Trienio $trienio, $curso)
{   
    $trienios = $trienio->whereHas('curso', function ($query) use ($curso) {
        $query->where('curso', '=', $curso);
    })->get();

    return view('trienio')->withTrienios($trienios);
}

PapController.php

public function index(Pap $pap, $curso, $trienio)
{
    $pap = $pap->whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        });
    })->toSql();

    dd($pap);

    return view('pap')->withPap($pap);

}

public function show(Pap $pap, $curso, $trienio, $id)
{   
    $pap = $pap->find($id);

    dd($pap);

    return view('show')->withPap($pap);
}

正如您所看到的,在PAP控制器的索引方法的情况下,请求数据的查询是一个巨大的混乱,是n + 1问题的缩影:

"select * from `pap` where exists (select * from `alunos` where `pap`.`aluno_id` = `alunos`.`id` and exists (select * from `trienios` where `alunos`.`trienio_id` = `trienios`.`id` and `data_trienio` = ? and exists (select * from `cursos` where `trienios`.`curso_id` = `cursos`.`id` and `curso` = ?)))"

我打算用这个查询来获取与trienio记录相关的PAP记录,而trienio记录又基于用户在url中输入的输入与curso记录相关(我已经展示了一个例子)上面),问题是,因为我一般是这个东西的新手,我无法将急切的加载概念应用于我想要运行的查询

我也在验证用户可以输入以下内容的网址时遇到问题:

http://localhost:8000/qwfkjnfwq/qjqtikjn/1

并且控制器方法show将获取pap记录,而不考虑用户输入2级以上的参数,这显然会造成“安全”问题

我想做的是:

http://localhost:8000/TGEI/2014-2017/1

控制器方法show将加载aluno.trienio嵌套关系,然后根据2014-2017参数获取与aluno模型相关的trienio id,然后根据trienio模型获取与trienio模型相关的curso id。 TGEI参数

等等,像这样的东西

http://localhost:8000/qwfkjnfwq/qjqtikjn/1

将失效而不是通过。

这可能是一个棘手的问题,但无论谁能帮助我,我都会感谢。我明白我的问题的某些部分可能不清楚(更多是因为英语不是我的第一语言),在这种情况下,我可以随心所欲地澄清它们。

有关更好的信息,请参阅我的web.php文件

Route::get('/', 'CursoController@index');
Route::get('/{curso}', 'TrienioController@index');
Route::get('/{curso}/{trienio}', 'PapController@index');
Route::get('/{curso}/{trienio}/{id}', 'PapController@show');

1 个答案:

答案 0 :(得分:2)

好的扩展我的评论。

使用Laravel 5.2 route model binding,您可以在控制器方法中注入模型(如下所示:public function show(Pap $pap)),Laravel会自动获取Pap模型,其中包含url(基本上执行Pap::find($id)并将返回保存到$pap变量中)。这并不总是你想要的,因为你经常想要执行更复杂的查询。

我建议你不要在你的情况下使用路由模型绑定,只需自己进行查询。这样的事情(看我如何从控制器功能中删除模型)

// CursoController.php
public function index()
{
    $cursos = Curso::all();

    return view('curso')->withCursos($cursos);
}

// TrienioController.php
public function index($curso)
{   
    $trienios = Trienio::whereHas('curso', function ($query) use ($curso) {
        $query->where('curso', '=', $curso);
    })->get();

    return view('trienio')->withTrienios($trienios);
}

// Pap controller
public function index($curso, $trienio)
{
    $pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        });
    })->get();

    return view('pap')->withPap($pap);

}

public function show($curso, $trienio, $id)
{   
    $pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        });
    })->findOrFail($id);

    return view('show')->withPap($pap);
}

另请注意,在show()方法中,我几乎复制了index()查询,这是验证。

关于查询的优化 - 你拥有它们的查询绝对没问题。现在没有n + 1问题。

如果您要对其中一个索引结果执行foreach并调用子属性,则会出现n + 1问题。例如,如果您在pap视图中执行此类操作:

@foreach($pap as $p)
<div>{{ $p->aluno->id }}</div>
@endforeach

这将为$p中的每个$pap创建一个新查询,以获取相关的aluno结果。

要避免此n + 1问题,您必须在循环中使用之前加载数据。您会急切地使用->with(relationship)方法加载数据。像这样:

// Pap controller
public function index($curso, $trienio)
{
    $pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        });
    })
    ->with('aluno.trienio') // You might need some additional checks here, depending on you needs
    ->get();

    return view('pap')->withPap($pap);
}

它并不完全直观,但->whereHas(relationship)不会急切加载关系。所以你经常会发现自己写这样的陈述:

// Pap controller
public function index($curso, $trienio)
{
    $pap = Pap::whereHas('aluno.trienio', function ($query) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        });
    })
    ->with(['aluno.trienio' => function ($q) use ($curso, $trienio) {
        $query->where('data_trienio', '=', $trienio)->whereHas('curso', function ($query) use ($curso) {
            $query->where('curso', '=', $curso);
        }]); // These are the additional checks
    ->get();

    return view('pap')->withPap($pap);
}