JeuWeb - Crée ton jeu par navigateur
Data Access Object (DAO) - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Data Access Object (DAO) (/showthread.php?tid=4410)



Data Access Object (DAO) - My Hotel - 14-10-2009

Salut à tous!

J'hésite en ce moment à utiliser des DAO... Qu'en pensez-vous?
Utile ou pas pour un jeu PHP? Quels sont les avantages, inconvénients? Comment vous en servez-vous?

Voilà, j'espère que ça va m'aider à me décider Wink

Bye


RE: Data Access Object (DAO) - Sephi-Chan - 14-10-2009

J'aime les DAO, j'utilise ActiveRecord (c'est celui par défaut de Ruby on Rails). Pour un jeu par navigateur, les avantages sont conséquents et les défauts moindres.

Parmi les avantages les plus évident : la facilité de maintenance et la fiabilité.

Je prends l'exemple d'un modèle de mon jeu (à l'heure où j'écris les lignes, il est encore assez basique). Il s'agit du modèle Neighbourhood qui modélise un lien de voisinage (unilatéral) entre deux territoires.


class Neighbourhood < ActiveRecord::Base

belongs_to :territory
belongs_to :neighbour, :class_name => 'Territory',
:foreign_key => :neighbour_id

validate :check_uniqueness_of_association
validate Confusedource_and_neighbour_are_different

protected

# Can't have to neighbourhoods with the same territories.
def check_uniqueness_of_association
existant_equivalent = Neighbourhood.find_by_territory_id_and_neighbour_id(
territory_id,
neighbour_id
)

errors.add_to_base(:already_defined) if existant_equivalent
end

# The source territory can't be the neighbour territory.
def source_and_neighbour_are_different
errors.add_to_base(Confusedame_territories) if territory == neighbour
end

end

D'une, on peut spécifier les association du modèle. Si j'ai un objet Neighbourhood, je peux facilement récupérer son territoire source (en appelant la méthode territory) et son territoire de destination (avec la méthode neighour). Cela me renverra des objets Territory qui auront eux-même leurs propres associations (par exemple en appelant la méthode neighbours sur un objet territoire, j'obtiens tous les territoires associés (grâce à une relation plusieurs-à-plusieurs qui utilise Neighbourhood comme modèle de de jointure).

De plus, les règles de validations rendent le modèle fiable. C'est centralisé, on ne risque jamais de l'oublier.

En plus des règles de validation (très riches), on peut mettre des callbacks (avant/après la validation, avant/après la sauvegarde (et on peut différencier création et modification), avant/après la destruction, etc), donc à nouveau ça permet une centralisation très claire.

Parmi les défauts, on a les performances. Bien sûr si je fais User.all avec une table de 15 000 utilisateurs, ça va prendre un peu de temps d'instancier autant d'objets, mais on fait rarement ce genre de choses…

L'autre avantage aussi, c'est que l'objet permet très simplement d'ajouter un système de cache : il suffit de modifier les accesseurs.

Enfin, car ça peut en rebuter, le SQL est généré intelligemment.
Il y a moyen de faire beaucoup de choses avant d'avoir besoin de taper du SQL à la main (et ça reste très simple à faire).

Pour les sceptiques à propos de l'efficacité des requêtes générée, une petite vidéo qui montre en action deux outils qui permettent de générer des jointures include et join (ou non, selon les cas).


Sephi-Chan


RE: Data Access Object (DAO) - Anthor - 15-10-2009

(14-10-2009, 07:51 PM)Sephi-Chan a écrit : Enfin, car ça peut en rebuter, le SQL est généré intelligemment.
Il y a moyen de faire beaucoup de choses avant d'avoir besoin de taper du SQL à la main (et ça reste très simple à faire).

Je rajouterais qu'il est aussi souvent plus propre et mieux optimisé que celui écrit à la main Smile


RE: Data Access Object (DAO) - My Hotel - 18-10-2009

Et au niveau des jointures, comment ça se passe, sans framework bien sûr... Parce que je pense que c'est malheureusement plus compliqué qu'un simple " belongs_to :territory ", non? Smile


RE: Data Access Object (DAO) - Sephi-Chan - 18-10-2009

Ça se fait de manière transparente :


@map = Map.find(1) # Retourne un objet Map.
# SELECT *
# FROM `maps`
# WHERE (`maps`.`id` = 1)

# On récupère les territoires grâce à l'association has_many :territories.
@territories = @map.territories # Retourne un tableau d'objets Territory.
# SELECT *
# FROM `territories`
# WHERE (`territories`.map_id = 1)

# On récupère les territoires voisins du premier territoire.
# On a une relation many-to-many, on passe par un modèle de jointure (nommé Neighourhood).
@territories.first.neighbours
# SELECT `territories`.*
# FROM `territories`
# INNER JOIN `neighbourhoods` ON `territories`.id = `neighbourhoods`.neighbour_id
# WHERE ((`neighbourhoods`.territory_id = 2))

Par défaut, Ruby on Rails fait du lazy loading, mais si tu veux charger d'un coup les données (par exemple pour charger en une seule fois des news et leurs commentaires, pour pas que le chargement à la volée fasse une requête par news pour choper les commentaires) :


# On récupère la carte et ses territoires.
@map = Map.find(1, :include => :territories)
# Il fait deux requêtes, gentiment.
# SELECT *
# FROM `maps`
# WHERE (`maps`.`id` = 1)
#
# SELECT `territories`.*
# FROM `territories`
# WHERE (`territories`.map_id = 1)



# On récupère la carte, ses territoires puis les voisins de ces territoires (fiou !).
@map = Map.find(1, :include => { :territories => :neighbours })

# SELECT *
# FROM `maps`
# WHERE (`maps`.`id` = 1)
#
# SELECT `territories`.*
# FROM `territories`
# WHERE (`territories`.map_id = 1)
#
# SELECT `neighbourhoods`.*
# FROM `neighbourhoods`
# WHERE (`neighbourhoods`.territory_id IN (2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30))
#
# SELECT *
# FROM `territories`
# WHERE (`territories`.`id` IN (11,23,7,12,24,8,13,14,25,9,15,26,27,16,28,17,18,2,30,19,20,3,21,4,10,5))



Sephi-Chan


RE: Data Access Object (DAO) - My Hotel - 18-10-2009

Merci pour cette réponse, mais c'est moi où au finale, ça fait plus de requêtes? Et si oui, est-ce que ça vaut vraiment le coup en PHP, ou y'a qu'en RoR que c'est génial Wink


RE: Data Access Object (DAO) - Sephi-Chan - 18-10-2009

Ça fait les requêtes de manière plus intelligentes. Cela dit, il faut que le développeur soit malin.

Admettons qu'on ai ce modèle, tout simple :


# Dans le modèle :
class Post < ActiveRecord::Base
@@per_page = 20
cattr_reader :per_page

default_scope :order => 'created_at DESC'
named_scope :published,
:conditions => [ 'published = ?', true ]

has_many :comments
end

Ça dit quoi ?
  • Que quand on va paginer, ça va mettre 20 enregistrements par page (c'est une limitation propre au modèle, donc on met ce chiffre en tant que variable de classe. On génère ensuite un accesseur (au niveau de la classe) en lecture pour cette variable ;
  • On dit que par défaut, à chaque fois qu'on récupérera des posts, ils seront triés par date de création ;
  • On génère une scope qui nous permettra de filtrer nos posts. En faisant Post.published.all, on aura tous les posts publiés ;
  • On dit qu'un post peut avoir plusieurs commentaires.


Dans le contrôleur :


class PostsController < ApplicationController
def index
@posts = Post.published.paginate(:page => param[:page])
end
end

Là aussi, c'est simple : on sélectionne les posts publiés en paginant. On indique que le paramètre d'URL qui indique la page parcourue se nomme "page". On met tout ça dans une variable d'instance (comme l'indique le @) @posts, qui sera donc accessible dans la vue.


Dans la vue :


<div id="posts">
<% for post in @posts %>
<% content_tag_for(:div, post) do %>
<h2><%= h(post.title) %></h2>
<div class="comments_count">
<%= link_to pluralize(post.comments.count, 'commentaire'),
post_path(post, :anchor => 'comments')
%>
</div>
<div class="content">
<%= simple_format(h(post.content)) %>
</div>
<% end %>
<% end %>
</div>

<%= will_paginate(@posts) %>

  • Pour chaque post, on crée un bloc div le content_tag_for sert à générer un div qui devine la classe et l'identifiant du bloc. Ainsi, ici ce sera quelque chose comme <div class="post" id="post-23">...</div> ;
  • On affiche le nombre de commentaires (mis au pluriel si besoin) en tant que lien pour afficher le détail de l'annonce avec une ancre pour aller vers les commentaires en bas ;
  • On échappe les contenus avec le helper h() (un alias de html_escape()) ;
  • On affiche si besoin les liens de pagination ;


Le problème, c'est que Rails va lancer une requête pour chaque post, puisqu'à chaque itération, on va lui demander de compter les commentaires ! On va donc avoir un total de (nombre de posts + 1) requête !

Pour n'avoir que 2 requêtes (une pour récupérer les posts et une autre pour les commentaires de ces posts), on va modifier un peu le contrôleur :


class PostsController < ApplicationController
def index
@posts = Post.published.paginate(:page => param[:page], :include => :comments)
end
end

Et voilà ! Smile

Et les ORM pour les autres langages, je suppose qu'elles sont bonnes, mais je ne les ai jamais utilisé "sérieusement" donc je ne peux pas me prononcer à leur sujet.

Ce que j'aime dans ActiveRecord, c'est son intégration avec Rails, la création très simple de liens, la génération éléments HTML avec les attributs class et id qui vont bien, etc.


Sephi-Chan


RE: Data Access Object (DAO) - Hakushi - 22-10-2009

Bien que le DAO ait ses avantages, il faut savoir reconnaitre quand il est necessaire ou non d'y avoir recours. J'y ai par exemple beaucoup recourt dans du back-end (CMS majoriterement), via Zend Framework (dont l'implementation ressemble beaucoup a ActiveRecord), alors qu'a l'opposé, sous un lourd traffic (surement hors de consideration dans le cas present, mais sait on jamais), on essaye d'exclure au maximum le nombre de couche du site, couches qui créent une charge souvent inutile.

Ce sont des pratiques tout a fait valable, il faut simplement en connaitre les limites et quand les utiliser.