339 votes

Bon Référentiel de Conception de Modèle en PHP?

Préface: je suis attemping à utiliser le modèle de référentiel dans l'architecture MVC avec les bases de données relationnelles.

J'ai récemment commencé l'apprentissage du TDD en PHP, et je me suis rendu compte que ma base de données est couplée beaucoup trop étroite collaboration avec le reste de ma demande. J'ai lu sur des référentiels, et à l'aide d'un conteneur IoC à "injecter" dans mes contrôleurs. Des trucs très cools. Mais maintenant vous poser quelques questions pratiques au sujet de la conception du dépôt. Considérons l'exemple suivant.

<?php

class DbUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function findAll()
    {
    }

    public function findById($id)
    {
    }

    public function findByName($name)
    {
    }

    public function create($user)
    {
    }

    public function remove($user)
    {
    }

    public function update($user)
    {
    }
}

Question n ° 1: Trop de champs

Toutes ces méthodes utilisent une sélectionnez tous les champs (SELECT *). Cependant, dans mes applications, j'essaie toujours de limiter le nombre de champs que je reçois, comme souvent, cela ajoute de la surcharge et ralentit les choses. Pour ceux qui utilisent ce modèle, comment réagissez-vous face à cela?

Question n ° 2: Trop de méthodes

Alors que cette classe a l'air joli, maintenant, je sais que dans un monde réel de l'application, j'ai besoin de beaucoup plus de méthodes. Par exemple:

  • findAllByNameAndStatus
  • findAllInCountry
  • findAllWithEmailAddressSet
  • findAllByAgeAndGender
  • findAllByAgeAndGenderOrderByAge
  • Etc.

Comme vous pouvez le voir, il pourrait être très, très longue liste de méthodes possibles. Et puis, si vous ajoutez dans le champ de sélection de la question ci-dessus, le problème s'aggrave. Dans le passé, j'avais normalement il suffit de mettre tous cette logique dans mon controller:

<?php

class MyController
{
    public function users()
    {
        $users = User::select('name, email, status')->byCountry('Canada')->orderBy('name')->rows()

        return View::make('users', array('users' => $users))
    }

}

Avec mon référentiel approche, je ne veux pas finir avec ceci:

<?php

class MyController
{
    public function users()
    {
        $users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');

        return View::make('users', array('users' => $users))
    }

}

Question n ° 3: Impossible de faire correspondre une interface

Je vois l'avantage de l'utilisation des interfaces pour les dépôts, donc je peux échanger mes de mise en œuvre (à des fins de test ou autres). Ma compréhension des interfaces, qu'elles définissent un contrat qu'une mise en œuvre doit suivre. C'est génial jusqu'à ce que vous commencez à ajouter des méthodes supplémentaires pour vos dépôts comme findAllInCountry(). Maintenant, j'ai besoin de mettre à jour mon interface d'avoir aussi cette méthode, sinon d'autres implémentations peut-être pas, et qui pourraient briser ma demande. Par ce sent fou...un cas de la queue qui remue le chien.

Spécification Modèle?

Cela me mène à croire que le référentiel ne devrait avoir qu'un nombre fixe de méthodes (comme save(), remove(), find(), findAll(), etc). Mais alors, comment puis-je effectuer des recherches? J'ai entendu parler de la Spécification du Modèle, mais il me semble que cela ne réduit tout un ensemble d'enregistrements (via IsSatisfiedBy()), ce qui est clairement les principaux problèmes de performances si vous êtes en tirant à partir d'une base de données.

De l'aide?

Clairement j'ai besoin de repenser les choses un peu lorsque l'on travaille avec des dépôts. Quelqu'un peut-il éclairer sur la façon dont cela se fait?

240voto

Jonathan Points 2381

Je pensais prendre une fissure à répondre à ma propre question. Ce qui suit est juste une façon de résoudre les questions 1 à 3 dans ma question initiale.

Avertissement: je ne peut pas toujours utiliser les bons termes lors de la description des modèles ou techniques. Désolé pour ça.

Les Objectifs:

  • Créer un exemple complet d'un contrôleur de base pour la visualisation et l'édition d' Users.
  • Tout le code doit être entièrement vérifiés et mockable.
  • Le contrôleur doit avoir aucune idée de l'endroit où sont stockées les données (ce qui signifie qu'il peut être changé).
  • Exemple pour montrer un SQL de mise en œuvre (les plus courantes).
  • Pour des performances maximales, les contrôleurs devraient recevoir uniquement les données dont ils ont besoin-pas de champs supplémentaires.
  • La mise en œuvre devrait tirer parti de certains type de mapper des données pour faciliter le développement.
  • Mise en œuvre devrait avoir la capacité d'exécuter le complexe de données de recherches.

La Solution

Je vais partager mon stockage persistant (base de données) de l'interaction en deux catégories: R (Lecture) et de la CUD (Create, Update, Delete). Mon expérience a été que les lectures sont vraiment quelles sont les causes d'une demande de ralentir. Et tandis que la manipulation de données (CUD) est en fait plus lentement, il se passe beaucoup moins souvent, et est donc beaucoup moins d'inquiétude.

De la CUD (Create, Update, Delete) est facile. Ceci implique une collaboration avec de véritables modèles, qui sont ensuite transmis à mes Repositories pour la persistance. Remarque, mon référentiels de toujours fournir une méthode de Lecture, mais simplement pour la création d'objet, pas d'affichage. Plus sur cela plus tard.

R (Lu) n'est pas si facile. Pas de modèles ici, juste des objets de valeur. Utiliser des tableaux si vous préférez. Ces objets peuvent représenter un modèle unique ou un mélange de plusieurs modèles, n'importe quoi vraiment. Ces ne sont pas très intéressantes sur leur propre, mais comment ils sont générés. J'utilise ce que j'appelle Query Objects.

Le Code:

Modèle Utilisateur

Commençons simple avec notre base de modèle d'utilisateur. Notez qu'il n'y a pas d'ORM extension ou la base de données des trucs à tous. Juste pur modèle de gloire. Ajouter votre getters, setters, de validation, de quoi que ce soit.

class User
{
    public $id;
    public $first_name;
    public $last_name;
    public $gender;
    public $email;
    public $password;
}

Référentiel D'Interface

Avant que je créer mon dépôt, je veux créer mon référentiel de l'interface. Cela permettra de définir le "contrat" que les référentiels doivent suivre dans l'ordre pour être utilisé par mon contrôleur. Rappelez-vous, mon contrôleur ne sais pas où les données sont stockées.

Notez que mon référentiels que tous contiennent ces trois méthodes. L' save() méthode est à la fois responsable de la création et de la mise à jour des utilisateurs, tout simplement en fonction de si oui ou non l'objet utilisateur dispose d'un identifiant.

interface UserRepositoryInterface
{
    public function find($id);
    public function save(User $user);
    public function remove(User $user);
}

SQL Référentiel de mise en Œuvre

Maintenant, pour créer mon implémentation de l'interface. Comme mentionné, mon exemple ne pouvait être qu'avec une base de données SQL. Remarque l'utilisation d'un mapper des données pour éviter d'avoir à écrire répétitif des requêtes SQL.

class SQLUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function find($id)
    {
        // Find a record with the id = $id
        // from the 'users' table
        // and return it as a User object
        return $this->db->find($id, 'users', 'User');
    }

    public function save(User $user)
    {
        // Insert or update the $user
        // in the 'users' table
        $this->db->save($user, 'users');
    }

    public function remove(User $user)
    {
        // Remove the $user
        // from the 'users' table
        $this->db->remove($user, 'users');
    }
}

Objet De Requête De L'Interface

Maintenant, avec la CUD (Create, Update, Delete) pris en charge par notre référentiel, nous pouvons nous concentrer sur la R (Read). Les objets de requête sont tout simplement une encapsulation de certains types de données logique de recherche. Ils sont pas générateurs de requêtes. En faisant abstraction comme notre référentiel, nous pouvons changer la mise en oeuvre et tester plus facilement. Un exemple d'un Objet de Requête peut être un AllUsersQuery ou AllActiveUsersQuery, ou même MostCommonUserFirstNames.

Vous pensez peut-être "je ne peux pas juste de créer des méthodes dans mes dépots à ces requêtes?" Oui, mais ici, c'est pourquoi je ne le fais pas:

  • Mes dépôts sont conçus pour travailler avec des objets de modèle. Dans un monde réel, app, pourquoi aurais-je besoin d'obtenir l' password si je suis à la recherche de la liste de tous mes utilisateurs?
  • Les dépôts sont souvent des modèles spécifiques, mais les requêtes impliquent souvent plus d'un modèle. Donc, ce référentiel ne vous mettez votre méthode?
  • Ce qui me permet de garder des référentiels très simple-pas de ballonnement classe de méthodes.
  • Toutes les requêtes sont désormais organisés dans leurs propres classes.
  • Vraiment, à ce stade, les référentiels existent tout simplement abstraction de ma couche de base de données.

Pour mon exemple, je vais créer un objet de requête de recherche "AllUsers". Voici l'interface:

interface AllUsersQueryInterface
{
    public function fetch($fields);
}

Objet De Requête De Mise En Œuvre

C'est là que nous pouvons utiliser un mappeur de données de nouveau pour aider à accélérer le développement. Notez que je suis en permettant un réglage pour le retour de l'ensemble de données-les champs. C'est à peu près aussi loin que je veux aller à la manipulation de la requête. Souvenez-vous, mes objets de requête ne sont pas générateurs de requêtes. Ils suffit d'effectuer une requête spécifique. Cependant, depuis que je sais que je vais probablement utiliser beaucoup celle-ci, dans un certain nombre de situations différentes, je me donne la possibilité de spécifier les champs. Je ne veux plus jamais retourner les champs je n'ai pas besoin!

class AllUsersQuery implements AllUsersQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch($fields)
    {
        return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
    }
}

Avant de passer à la manette, je veux montrer un autre exemple pour illustrer la puissance de ce qui est. J'ai peut-être un moteur de génération de rapports et la nécessité de créer un rapport pour AllOverdueAccounts. Ce qui peut être délicat avec mon mapper des données, et je veux écrire quelques - SQL dans cette situation. Pas de problème, voici ce que cet objet de requête pourrait ressembler à:

class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch()
    {
        return $this->db->query($this->sql())->rows();
    }

    public function sql()
    {
        return "SELECT...";
    }
}

Ce bien garde toute ma logique pour ce rapport dans une classe, et il est facile de tester. Je peux moquer de mon cœur le contenu, ou même utiliser une mise en œuvre différente entièrement.

Le Contrôleur

Maintenant la partie amusante-rassembler tous les morceaux ensemble. Notez que je suis à l'aide de l'injection de dépendance. Généralement, les dépendances sont injectés dans le constructeur, mais je préfère de les injecter dans mes méthodes de contrôleur (routes). Cela réduit le contrôleur de l'objet graphique, et je trouve cela plus lisible. Remarque, si vous n'aimez pas cette approche, il suffit d'utiliser la traditionnelle méthode de constructeur.

class UsersController
{
    public function index(AllUsersQueryInterface $query)
    {
        // Fetch user data
        $users = $query->fetch(['first_name', 'last_name', 'email']);

        // Return view
        return Response::view('all_users.php', ['users' => $users]);
    }

    public function add()
    {
        return Response::view('add_user.php');
    }

    public function insert(UserRepositoryInterface $repository)
    {
        // Create new user model
        $user = new User;
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the new user
        $repository->save($user);

        // Return the id
        return Response::json(['id' => $user->id]);
    }

    public function view(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('view_user.php', ['user' => $user]);
    }

    public function edit(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('edit_user.php', ['user' => $user]);
    }

    public function update(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Update the user
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the user
        $repository->save($user);

        // Return success
        return true;
    }

    public function delete(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Delete the user
        $repository->delete($user);

        // Return success
        return true;
    }
}

Réflexions Finales:

Les choses importantes à noter ici sont que lorsque je fais des modifications (création, mise à jour ou suppression) d'entités, je suis en train de travailler avec de vrais objets de modèle, et l'accomplissement de la persistance à travers mes dépôts.

Cependant, quand je suis à l'affichage (sélection des données et de les envoyer sur le point de vue) je ne travaille pas avec des objets de modèle, mais plutôt de la plaine de vieux objets de valeur. Je sélectionner uniquement les champs dont j'ai besoin, et il est conçu de sorte que je peux au maximum mes données de recherche de performance.

Mon dépôts séjour très propre, et, au lieu de ce "désordre" est organisé dans mon modèle de requêtes.

J'utilise un mappeur de données à l'aide au développement, comme c'est juste ridicule d'écrire répétitif SQL pour les tâches courantes. Cependant, vous ne pouvez absolument écrire de SQL en cas de besoin (les requêtes compliquées, rapports, etc.). Et quand vous faites, il est bien rangé dans un bien nommé de la classe.

J'aimerais entendre votre point de vue sur mon approche!

53voto

ryan1234 Points 4884

Basé sur mon expérience, voici quelques réponses à vos questions:

Q: Comment faire face à ramener les champs nous n'avons pas besoin?

A: D'après mon expérience ce vraiment résume à traiter avec compléter entités par rapport à des requêtes ad-hoc.

Une entité complète est quelque chose comme un User objet. Elle a des propriétés et des méthodes, etc. C'est un citoyen de première classe dans votre base de code.

Un ad-hoc requête retourne des données, mais nous ne savons rien au-delà. Comme les données sont transmises autour de l'application, il est sans contexte. Est-il un User? Un User certains Order d'information en annexe? Nous ne savons pas vraiment.

Je préfère travailler avec plein d'entités.

Vous avez raison, vous aurez souvent à ramener les données que vous n'utiliserez pas, mais vous pouvez le faire de différentes façons:

  1. Agressivement mettre en cache les entités de sorte que vous ne payez que la lecture des prix à la fois à partir de la base de données.
  2. Passer plus de temps à la modélisation de vos entités, donc ils ont de bonnes distinctions entre eux. (Pensez à diviser une grande entité en deux entités plus petites, etc.)
  3. Envisager d'avoir plusieurs versions d'entités. Vous pouvez avoir un User pour le back-end et peut-être un UserSmall pour les appels AJAX. On peut avoir 10 propriétés et on a 3 propriétés.

Les inconvénients de travailler avec des requêtes ad-hoc:

  1. Vous vous retrouvez avec essentiellement les mêmes données sur de nombreuses requêtes. Par exemple, avec un User, vous finirez par écrit essentiellement le même select * pour les nombreux appels. Un appel obtiendrez 8 de 10 champs, on aura droit à 5 de 10, on obtient 7 de 10. Pourquoi ne pas remplacer tous avec un appel qui obtient 10 sur 10? La raison de ce qui est mauvais, c'est que c'est le meurtre de re-facteur/test/maquette.
  2. Il devient très difficile de raisonner à un niveau élevé au sujet de votre code au fil du temps. Au lieu de déclarations telles que "Pourquoi l' User - elle si lente?", en fin de la traque des requêtes uniques et ainsi de corrections de bugs ont tendance à être petites et localisées.
  3. Il est vraiment difficile de remplacer la technologie sous-jacente. Si vous stocker le tout dans MySQL maintenant et que vous voulez passer à MongoDB, c'est beaucoup plus difficile à remplacer 100 ad-hoc appels que c'est une poignée d'entités.

Q: je vais avoir un trop grand nombre de méthodes dans mon référentiel.

R: je n'ai pas vraiment vu le moyen de contourner ce problème autre que la consolidation des appels. Les appels de méthode dans votre référentiel vraiment la carte pour les fonctionnalités de votre application. Le plus de fonctionnalités, plus les données des appels spécifiques. Vous pouvez le pousser à revenir sur les caractéristiques et essayez de fusionner des appels similaires en un seul.

La complexité à la fin de la journée doit exister quelque part. Avec un modèle de référentiel, nous avons poussé dans le référentiel de l'interface au lieu de peut-être faire un tas de procédures stockées.

Il m'arrive parfois de me dire, "eh Bien, il a dû donner à quelque part! Il n'y a pas de solution miracle."

5voto

WMeldon Points 345

Je vais ajouter un peu sur ce que je suis en train d'essayer de saisir tout cela moi-même.

#1 et 2

C'est un endroit parfait pour votre ORM pour faire le gros du travail. Si vous utilisez un modèle qui met en œuvre une sorte de l'ORM, vous pouvez simplement utiliser de méthodes pour prendre soin de ces choses. Faire votre propre orderBy fonctions qui implémentent l'Éloquent méthodes si vous en avez besoin. En utilisant par exemple Éloquent:

class DbUserRepository implements UserRepositoryInterface
{
    public function findAll()
    {
        return User::all();
    }

    public function get(Array $columns)
    {
       return User::select($columns);
    }

Ce que vous semblez être à la recherche de est un ORM. Aucune raison de votre Dépôt ne peut pas être basé autour d'un. Ce serait exiger de l'Utilisateur de prolonger éloquent, mais personnellement, je ne vois pas cela comme un problème.

Si vous ne souhaitez éviter un ORM, vous auriez alors à "rouler" pour obtenir ce que vous cherchez.

#3

Les Interfaces ne sont pas censés être dur et rapide des exigences. Quelque chose peut implémenter une interface et d'y ajouter. Ce qu'il ne peut pas faire est de ne pas mettre en œuvre une fonction requise de cette interface. Vous pouvez également étendre les interfaces des classes comme les choses pour les garder au SEC.

Cela dit, je viens juste de commencer à appréhender, mais ces réalisations m'ont aidé.

3voto

Logan Bailey Points 748

Ces quelques solutions que j'ai vu. Il y a des avantages et des inconvénients pour chacun d'eux, mais c'est à vous de décider.

Question n ° 1: Trop de champs

C'est un aspect important, surtout lorsque vous prenez en compte l' Indice de la Seule Analyses. Je vois deux solutions pour faire face à ce problème. Vous pouvez mettre à jour vos fonctions à prendre en option un paramètre de type tableau contenant une liste de colonnes de retour. Si ce paramètre est vide, vous souhaitez renvoyer toutes les colonnes dans la requête. Cela peut être un peu bizarre; basé sur le paramètre que vous pourriez récupérer un objet ou un tableau. Vous pouvez également dupliquer l'ensemble de vos fonctions, de sorte que vous avez deux fonctions distinctes que exécutez la même requête, mais on renvoie un tableau de colonnes et l'autre renvoie un objet.

public function findColumnsById($id, array $columns = array()){
    if (empty($columns)) {
        // use *
    }
}

public function findById($id) {
    $data = $this->findColumnsById($id);
}

Question n ° 2: Trop de méthodes

J'ai brièvement travaillé avec l'ORM Propel il y a un an et c'est basé sur ce que je me souviens de cette expérience. Propel a la possibilité de créer sa structure de classe en fonction du schéma de base de données. Il crée deux objets pour chaque table. Le premier objet est une longue liste de fonction d'accès similaire à ce que vous avez actuellement répertoriés; findByAttribute($attribute_value). Le prochain objet hérite de ce premier objet. Vous pouvez mettre à jour cet enfant de l'objet à construire dans votre plus complexes de lecture fonctions.

Une autre solution serait d'utiliser __call() à la carte non les fonctions définies à quelque chose d'exploitable. Votre __call méthode serait serait en mesure d'analyser les findById et findByName dans différentes requêtes.

public function __call($function, $arguments) {
    if (strpos($function, 'findBy') === 0) {
        $parameter = substr($function, 6, strlen($function));
        // SELECT * FROM $this->table_name WHERE $parameter = $arguments[0]
    }
}

J'espère que cela aide au moins certains de ce que.

3voto

TFennis Points 477

Je ne peux commenter sur la façon dont nous (à mon entreprise) face à cela. Tout d'abord la performance n'est pas trop un problème pour nous, mais en ayant propre/bon code.

Tout d'abord, nous définissons les Modèles comme un UserModel qui utilise un ORM pour créer UserEntity objets. Lorsqu'un UserEntity est chargé à partir d'un modèle de tous les champs sont chargés. Pour les champs de référencement des entités étrangères nous utiliser le modèle étranger à créer des entités respectives. Pour ces entités, les données seront chargés à la demande. Maintenant, votre première réaction pourrait être ...???...!!! laissez-moi vous donner un exemple un peu un exemple:

class UserEntity extends PersistentEntity
{
    public function getOrders()
    {
        $this->getField('orders'); //OrderModel creates OrderEntities with only the ID's set
    }
}

class UserModel {
    protected $orm;

    public function findUsers(IGetOptions $options = null)
    {
        return $orm->getAllEntities(/*...*/); // Orm creates a list of UserEntities
    }
}

class OrderEntity extends PersistentEntity {} // user your imagination
class OrderModel
{
    public function findOrdersById(array $ids, IGetOptions $options = null)
    {
        //...
    }
}

Dans notre cas $db est un ORM qui est capable de charger les entités. Le modèle indique à l'ORM pour charger un ensemble d'entités d'un type spécifique. L'ORM contient une cartographie et l'utilise pour injecter tous les champs pour que l'entité de l'entité. Pour les étrangers champs, cependant, seulement l'identité de ces objets sont chargés. Dans ce cas, l' OrderModel crée OrderEntitys avec seulement l'id de référence des commandes. Lors de l' PersistentEntity::getField est appelée par l' OrderEntity l'entité indique c'est le modèle à chargement différé tous les champs dans l' OrderEntitys. Tous les OrderEntitys associé à un UserEntity sont traités comme un seul résultat et sera chargé à la fois.

La magie est ici que notre modèle et ORM injecter toutes les données dans les entités, et que les entités fournissent simplement des fonctions wrapper pour le générique getField méthode fournie par PersistentEntity. Pour résumer, nous avons toujours charger tous les champs, mais les champs de la référence d'une entité étrangère sont chargés lorsque nécessaire. Juste le chargement d'un tas de champs n'est pas vraiment un problème de performance. Charge possible des entités étrangères mais serait une ÉNORME baisse de la performance.

Maintenant, sur le chargement d'un ensemble spécifique d'utilisateurs, fondée sur une clause where. Nous fournissons un objet orienté ensemble de classes qui vous permettent de spécifier simple expression qui peuvent être collées ensemble. Dans l'exemple de code que j'ai nommé GetOptions. C'est un wrapper pour toutes les options possibles pour une requête select. Il contient un ensemble de clauses where, group by et tout le reste. Notre où les clauses sont assez complexes, mais vous pouvez bien évidemment faire une version plus simple facilement.

$objOptions->getConditionHolder()->addConditionBind(
    new ConditionBind(
        new Condition('orderProduct.product', ICondition::OPERATOR_IS, $argObjProduct)
    )
);

Une version plus simple de ce système serait de passer le cas OÙ une partie de la requête comme une chaîne de caractères directement sur le modèle.

Je suis désolé pour cette très compliqué de réponse. J'ai essayé de résumer notre cadre le plus rapidement et clairement possible. Si vous avez des questions supplémentaires, n'hésitez pas à demander et je vais mettre à jour ma réponse.

EDIT: de plus, si vraiment vous ne voulez pas charger certains domaines, tout de suite, vous pouvez spécifier un chargement différé option dans votre ORM cartographie. Parce que tous les champs sont finalement chargé par l' getField méthode, vous pouvez charger certains champs de dernière minute lorsque cette méthode est appelée. Ce n'est pas un très gros problème en PHP, mais je ne recommanderais pas pour les autres systèmes.

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