4 votes

problèmes liés à la validation des données et à l'optimisation des requêtes

Je crée un système de gestion où les enseignants peuvent gérer les projets finaux des étudiants et où les étudiants peuvent voir ce que les autres étudiants ont créé.

Je suis un nouveau venu dans le monde de Laravel et j'ai des problèmes pour optimiser les requêtes et valider les urls.

Voici mes schémas de tables :

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    |                |
+-----------+------------------+------+-----+---------+----------------+

Jusqu'à présent, j'ai réussi à mettre en place des urls dynamiques basées sur les enregistrements définis dans la table cursos et trienios, comme ceci : http://localhost:8000/TGEI/2014-2017

(TGEI étant un enregistrement dans la table cursos qui permet de récupérer les enregistrements trienio associés et 2014-2017 étant un enregistrement dans la table trienios qui est lié à un enregistrement curso dans une relation 1-to-many et qui permet de récupérer les enregistrements pap associés).

tout cela fonctionne bien, mais j'ai des difficultés à optimiser des requêtes extrêmement inefficaces qui deviendront un problème très important lorsque la base de données s'étoffera

voici mes relations :

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');
}

et ce sont les contrôleurs qui sont chargés de servir les pages accessibles à l'utilisateur :

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

comme vous pouvez le constater, dans le cas de la méthode d'indexation du contrôleur PAP, la requête qui demande les données est un énorme désordre qui est l'exemple même du problème 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` = ?)))"

L'objectif de cette requête est de récupérer les enregistrements PAP liés à un enregistrement trienio, lui-même lié à un enregistrement curso, en fonction des données saisies par l'utilisateur dans l'url (j'ai montré un exemple ci-dessus). Le problème est que, comme je suis un débutant dans ce domaine en général, je n'ai pas pu appliquer les concepts de chargement anticipé à la requête que je veux exécuter.

J'ai également un problème avec la validation des urls dans lesquelles l'utilisateur peut saisir ce qui suit :

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

et la méthode show du contrôleur récupérera un enregistrement pap sans tenir compte des paramètres que l'utilisateur a saisis deux niveaux plus haut, ce qui posera évidemment un problème de "sécurité".

et ce que je voulais faire, c'était.. :

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

la méthode du contrôleur show chargera la relation imbriquée aluno.trienio, puis récupérera l'identifiant trienio lié au modèle aluno conformément à l'élément 2014-2017 puis récupérer l'identifiant du curso lié au modèle trienio conformément au paramètre TGEI paramètre

et donc, des choses comme ça

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

serait invalidée au lieu d'être exécutée.

Je comprends que certaines parties de ma question peuvent ne pas être claires (d'autant plus que l'anglais n'est pas ma langue maternelle) et dans ce cas, je peux les clarifier autant que vous le souhaitez.

et pour une meilleure information, voici mon fichier web.php

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

2voto

devk Points 5775

D'accord pour développer mon commentaire.

Avec l'arrivée de Laravel 5.2 modèle d'itinéraire liaison qui vous permet d'injecter le modèle dans la méthode du contrôleur (comme ceci : public function show(Pap $pap) ) et Laravel récupérera automatiquement le fichier Pap avec l'identifiant dans l'url (en gros, faire Pap::find($id) et en enregistrant le retour dans $pap variable). Ce n'est pas toujours ce que l'on souhaite, car on veut souvent effectuer des requêtes plus complexes.

Je vous recommande de ne pas utiliser la liaison avec le modèle de route dans votre cas et de faire les requêtes vous-même. Quelque chose comme ceci (voir comment j'ai enlevé les modèles des fonctions du contrôleur)

// 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);
}

Il convient également de noter que dans le show() J'ai à peu près copié la méthode index() qui est la validation.

En ce qui concerne l'optimisation des requêtes, les requêtes telles qu'elles existent sont tout à fait correctes. Il n'y a pas de problème de n+1 en l'état.

Vous serez confronté au problème n+1 si vous effectuez une foreach sur l'un des résultats de l'index et en appelant les propriétés de l'enfant. Par exemple, si vous faites quelque chose comme cela dans un fichier pap vue :

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

Une nouvelle requête serait ainsi créée pour chaque $p en $pap pour obtenir les résultats d'aluno correspondants.

Pour éviter ce problème n+1, vous devez charger les données avant de les utiliser dans une boucle. Il est préférable de charger les données à l'aide de la fonction ->with(relationship) méthode. Quelque chose comme ceci :

// 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);
}

Ce n'est pas tout à fait intuitif, mais ->whereHas(relationship) n'aura pas la charge de la relation. Vous vous retrouverez donc souvent à écrire des déclarations de ce type :

// 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);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X