====== La classe SActiveRecord ======
ActiveRecord est un des designs patterns les plus simples pour faire de l'Object Relational Mapping. La [[http://www.martinfowler.com/eaaCatalog/activeRecord.html|définition]] qu'en donne Martin Fowler est :
//An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.//
Dans [[http://www.rubyonrails.org|RubyOnRails]], ce pattern est implémenté dans une seule classe (dôtée d'extensions) : afin de rechercher des enregistrements dans la base, on utilise une méthode statique ''find'' de la classe ActiveRecord. En raison de problèmes d'héritage avec les méthodes statiques en PHP5, il est difficile de conserver la même approche. Le pattern est ici donc implémenté à l'aide de 2 classes de base : ''SActiveRecord'' et ''SQuerySet'', qui fournit une interface pour charger des enregistrements depuis la base. Les instances de classes héritant de ''SActiveRecord'' encapsulent les enregistrements dans la base, et possèdent des méthodes de persistence : ''save()'', ''delete()'', ...
Concrètement, une classe héritant de ''SActiveRecord'' doit donc être créée pour chaque table dans la base.
class Article extends SActiveRecord
{
}
// Exemple d'utilisation :
$article = new Article();
$article->titre = 'Mon nouvel article';
$article->save();
Lors de son instanciation, la classe ''Article'', qui ne possède pas d'attributs définis, va récupérer la liste des champs directement dans la table, et en déduire ses attributs.
Vous pouvez alors créer un nouvel article, accéder à ses propriétés, et le sauver.
===== Manager =====
Lorsque vous générez une classe modèle à l'aide du [[generate|script de génération]], une propriété statique ''$objects'' est déclarée dans la classe :
class Article extends SActiveRecord
{
public static $objects;
}
Lorsque votre classe est incluse par Stato (automatiquement via ''__autoload'' ou manuellement à l'aide de la classe ''SDependencies''), une instance de la classe ''SManager'' sera assignée à cette propriété ''$objects''. Cette classe vous permet notamment de créer de nouveaux objets, ou bien de faire des [[queryset|requêtes]] :
$article = Article::$objects->create(array('titre' => 'Mon article', 'corps' => 'Bla bla bla...'));
$all_articles = Article::$objects->all();
===== Surcharge des attributs =====
Tous les champs d'un enregistrement wrappé dans une instance de ''SActiveRecord'' sont accessibles par des propriétés publiques, mais vous pouvez dans certains cas avoir besoin de faire certains traitements lors de la lecture/écriture de ces propriétés. Pour cela, il vous suffit de créer 2 méthodes ''read_()'' et ''write_()'' qui seront automatiquement appellées lors de l'accès aux propriétés. Ces méthodes devront utiliser ''write_attribute($key, $value)'' et ''read_attribute($key)'' pour accéder aux valeurs des champs.
class Song extends SActiveRecord
{
public function write_length($minutes)
{
$this->write_attribute('length', $minutes * 60);
}
public function read_length()
{
return $this->read_attribute('length') / 60;
}
}
===== Associations =====
Toujours afin de faciliter le travail du développeur, il vous est possible de définir d'éventuelles associations avec d'autres tables.
class Article extends SActiveRecord
{
public static $relationships = array
(
'comments' => 'has_many',
'author' => array('assoc_type' => 'belongs_to', 'class_name' => 'User')
);
}
**[[associations|En savoir plus]]**
===== Single table inheritance =====
''SActiveRecord'' permet l'héritage de classes modèles en stockant le nom de la classe dans un champ nommé ''type'' par défaut (vous pouvez le changer en déclarant dans votre classe parente une propriété statique publique ''$inheritance_field'').
class Company extends SActiveRecord {
public static $objects;
}
class Firm extends Company { }
class Client extends Company { }
Dans l'exemple ci-dessus, si vous créez une instance de la classe ''Firm'', elle sera sauvée dans la table ''companies'' avec ''Firm'' comme valeur du champ ''type''. De même, si vous faites un ''Company::$objects->get(...)'' et que la compagnie retournée est du type ''Client'', une instance de ''Client'' vous sera renvoyée.
**Important** : la propriété statique ''$objects'' ne doit être déclarée que dans la classe parente !
===== Callbacks =====
Les callbacks sont des crochets (hooks ?) dans le cycle de vie d'un ''SActiveRecord'' qui vous permettent d'exécuter du code avant ou après un changement d'état de l'objet. Cela vous permet par exemple de "préparer" certains attributs avant validation (et ainsi de permettre plusieurs syntaxes pour un même attribut, comme un numéro de téléphone).
* save
* is_valid
* **before_validate**
* validate
* **after_validate**
* **before_save**
* **before_create**
* create
* **after_create**
* **after_save**
===== Validation =====
Vous pouvez implémenter une validation des paramètres de vos classes métier en surchargeant les méthodes ''validate()'', ''validate_on_create()'' et ''validate_on_update()''. L'appel de ces méthodes se fait automatiquement lors de la sauvegarde d'un ''SActiveRecord''. Si la validation provoque des erreurs, la méthode ''save()'' renverra ''false'' et les erreurs seront disponibles dans la propriété ''$errors''.
class User extends SActiveRecord
{
public static $objects;
public function validate()
{
$this->validate_presence_of('username', 'password', 'mail');
$this->validate_format_of('mail', array('pattern' => 'email'));
$this->validate_format_of('password', array('pattern' => '/^[a-z0-9]{6,12}$/i', 'message' => 'Only alphanumerical characters plz !'));
$this->validate_length_of('username', array('min_length' => 4, 'max_length' => 20, 'message' => '4 to 20 chars plz !'));
$this->validate_inclusion_of('sex', array('choices' => array('M', 'F')));
}
public function validate_on_create()
{
$this->validate_confirmation_of('password', array('message' => 'Please confirm your password !'));
$this->validate_acceptance_of('terms_of_services');
}
}
Reportez-vous à l'API pour le détail des méthodes de validation disponibles.
===== Timestamps =====
''SActiveRecord'' peut enregister automatiquement les dates de création/mise à jour d'un objet si les champs ''created_on'' et ''updated_on'' sont présents dans la table et si la propriété ''$record_timestamps'' est ''true''.
===== Générer une classe modèle =====
Vous pouvez utiliser le script ''generate.php'' pour générer une classe modèle (héritant de ''SActiveRecord'') et un fichier de [[migration]] associé. Placez vous dans le répertoire de votre projet et utilisez la commande suivante :
php scripts/do.php generate model [model_name]
Par exemple :
php scripts/do.php generate model user
-> génèrera un fichier user.php dans app/models/ ainsi qu'un fichier 1_add_users.php dans db/migrate/