L'utilisation des filtres nécessite le module filter
qui :
query
dans le formulaire backoffice d'un documentLes filtres peuvent être utilisés dans différents contextes :
En fonction de ce que l'on attend d'un filtre, il implémente l'une, l'autre ou l'ensemble des méthodes suivantes :
getQuery
permettant de requêter les documents qui satisfont le filtrecheckValue
permettant de valider qu'un document donné satisfait le filtre
Ajouter une propriété de type Lob
dans le modèle du document.
Exemple :
<add name="query" type="Lob" min-occurs="1" />
Dans les fichier où cette propriété est éditable, c'est à dire, selon les cas : create.xml
et/ou properties.xml
et/ou localization.xml
, spécifier pour cette propriété les deux attributs suivants :
query
(le binding cQueryField du module filter sera alors utilisé)Exemples :
<!-- accepte les filtres sur les comptes clients --> <field name="query1" type="query" listid="customer/customer"/> <!-- accepte les filtres que sur les comptes clients sur lesquels à la fois getQuery et checkValue sont implémentés --> <field name="query2" type="query" listid="customer/customer::getQuery,checkValue"/>
Un champ du formulaire de type query
va permettre de construire une requête à l'aide de différents filtres. Ces filtres doivent être définis par code dans n'importe quel module : la commande change.php compile-document-filters
se charge de retrouver l'ensemble des filtres et de les grouper selon le modèle sur lequel ils portent. Un filtre est une classe PHP à placer dans le dossier monModule/persistentdocument/filters (seuls ces dossiers sont scannés par la commande de compilation des filtres).
Les filtres doivent implémenter l'interface f_persistentdocument_DocumentFilter
qui définit les différentes méthodes nécessaires. Une implémentation par défaut d'un certain nombre de ces méthodes est disponible, il s'agit de la classe f_persistentdocument_DocumentFilterImpl
. Dans la majorité des cas, c'est cette classe qu'étendra le filtre. Les méthodes à implémenter seront alors les suivantes :
getDocumentModelName()
: méthode statique qui renvoie simplement le modèle sur lequel ce filtre s'applique, sous la forme modules_monModule/monDocument.__construct()
: le constructeur se charge de définir et configurer les différents paramètres du filtre. Ces paramètres peuvent être de deux types différents : f_persistentdocument_DocumentFilterRestrictionParameter
) : l'utilisateur pourra paramétrer une restriction complète : type de restriction, champ sur lequel elle porte et valeur. On peut spécifier la liste des champs et des restrictions disponibles pour un paramètre donné ou bien les spécifier en dur (ils ne seront alors pas modifiable dans l'interface)f_persistentdocument_DocumentFilterValueParameter
) : l'utilisateur pourra simplement saisir une valeur qui sera ensuite utilisée pour écrire la requête. Le champ proposé sera mono-valué et pourra être de n'importe quel type standard (entier, flottant, document, chaine…). On définit ce type à l'aide d'une BeanPropertyInfo
qui peut être configurée à la main ou, s'il s'agit d'une propriété d'un document, récupérée directement d'un modèle. Attention, les contraintes de validation définies ne seront pas prises en compte, le champ sera juste obligatoire et validé avec via les validateurs par défaut du champgetQuery()
: cette méthode renvoie la query (instance de f_persistentdocument_criteria_Query
) correspondant au filtre. Celle-ci n'est supposée être appelée que sur un filtre où tous les paramètres ont été remplischeckValue($value)
: cette méthode prend en paramètre une instance de document et renvoie true
si elle satisfait le filtre, false
sinonOn trouve aussi dans certains filtres d'autres méthodes qu'a priori on n'implémentera pas sur un filtre spécifique d'un projet :
isHidden()
: si elle retourne true
, ce filtre ne sera pas sélectionnable dans les listes (utilisé pour déprécier des filtres sans pour autant casser les documents qui l'utilisent déjà)getAliases()
: renvoie un tableau de noms de classes considérées comme des alias de ce filtre. Ces noms correspondent à des classes qui n'existent pas physiquement, si ces noms sont retrouvés dans la configuration d'un document, un instance de ce filtre sera utilisée (utilisé lors du renommage de la classe d'un filtre, par exemple pour le changer de module, sans casser les documents qui l'utilisent déjà)Pour initialiser un nouveau filtre on utilise la commande :
change.php filter.add-filter <moduleName> <name> <modelName> [<commaSeparatedParamNames>]
Exemple :
La commande suivante génèrera un filtre MonFiltre
dans le module monmodule
portant sur le modèle modules_catalog/catalog
avec trois paramètres nommés respectivement param1
, param2
et param3
.
change.php filter.add-filter monmodule MonFiltre modules_catalog/catalog param1,param2,param3
On obtient alors la sortie suivante dans la console indiquant ce qui a été fait :
== AddFilter == Filter class path: /.../modules/monmodule/persistentdocument/filters/MonFiltreFilter.php Filter locales in m.monmodule.bo.documentfilters: monfiltrefilter-label and monfiltrefilter-text
class customer_BillingAddressFilter extends f_persistentdocument_DocumentFilterImpl { public function __construct() { $amountParameter = f_persistentdocument_DocumentFilterRestrictionParameter::getNewInstance(); $amountParameter->setAllowedPropertyNames(array( 'modules_customer/billingaddress.creationdate', 'modules_customer/billingaddress.title', 'modules_customer/billingaddress.firstname', 'modules_customer/billingaddress.lastname', 'modules_customer/billingaddress.email', 'modules_customer/billingaddress.company', 'modules_customer/billingaddress.addressLine1', 'modules_customer/billingaddress.addressLine2', 'modules_customer/billingaddress.addressLine3', 'modules_customer/billingaddress.zipCode', 'modules_customer/billingaddress.city', 'modules_customer/billingaddress.province', 'modules_customer/billingaddress.country', 'modules_customer/billingaddress.phone', 'modules_customer/billingaddress.fax', )); $this->setParameters(array('field' => $amountParameter)); } /** * @return String */ public static function getDocumentModelName() { return 'modules_customer/customer'; } /** * @return f_persistentdocument_criteria_Query */ public function getQuery() { $query = customer_CustomerService::getInstance()->createQuery(); $query->createCriteria('billingAddress')->add($this->getParameter('field')->getValueForQuery()); return $query; } }
class customer_UsedCouponCountFilter extends f_persistentdocument_DocumentFilterImpl { public function __construct() { $parameters = array(); $info = new BeanPropertyInfoImpl('count', 'Integer'); $parameter = f_persistentdocument_DocumentFilterRestrictionParameter::getNewHavingInstance($info); $parameters['count'] = $parameter; $this->setParameters($parameters); } /** * @return String */ public static function getDocumentModelName() { return 'modules_customer/customer'; } /** * @return f_persistentdocument_criteria_Query */ public function getQuery() { $query = customer_CustomerService::getInstance()->createQuery()->setFetchColumn('this'); $query->createCriteria('usedCoupon')->setProjection(Projections::rowCount('count')); $query->having($this->getParameter('count')->getValueForQuery()); return $query; } }
class customer_LoginFilter extends f_persistentdocument_DocumentFilterImpl { public function __construct() { $parameters = array(); $info = new BeanPropertyInfoImpl('mode', 'String'); $info->setLabelKey('mode de recherche'); $info->setListId('modules_filter/matchmodes'); $parameter = new f_persistentdocument_DocumentFilterValueParameter($info); $parameters['mode'] = $parameter; $info = new BeanPropertyInfoImpl('code', 'String'); $info->setLabelKey('chaine recherchée'); $parameter = new f_persistentdocument_DocumentFilterValueParameter($info); $parameters['code'] = $parameter; $this->setParameters($parameters); } /** * @return String */ public static function getDocumentModelName() { return 'modules_customer/customer'; } /** * @return f_persistentdocument_criteria_Query */ public function getQuery() { $dfs = f_persistentdocument_DocumentFilterService::getInstance(); $mode = $dfs->getMatchMode($this->getParameter('mode')->getValueForQuery()); $query = customer_CustomerService::getInstance()->createQuery(); $query->createCriteria('user')->add(Restrictions::like('login', $this->getParameter('code')->getValueForQuery(), $mode, true)); return $query; } }
class order_ShopOrderFilter extends f_persistentdocument_DocumentFilterImpl { public function __construct() { $parameters = array(); $info = new BeanPropertyInfoImpl('shop', 'modules_catalog/shop'); $shopParameter = new f_persistentdocument_DocumentFilterValueParameter($info); $parameters['shop'] = $shopParameter; $this->setParameters($parameters); } /** * @return String */ public static function getDocumentModelName() { return 'modules_order/order'; } /** * @return f_persistentdocument_criteria_Query */ public function getQuery() { $shopIds = DocumentHelper::getIdArrayFromDocumentArray($this->getParameter('shop')->getValueForQuery()); return order_OrderService::getInstance()->createQuery()->add(Restrictions::in('shopId', $shopIds)); } /** * @param customer_persistentdocument_customer $value * @return boolean */ public function checkValue($value) { if ($value instanceof order_persistentdocument_order) { $shopIds = DocumentHelper::getIdArrayFromDocumentArray($this->getParameter('shop')->getValueForQuery()); return in_array($value->getShopId(), $shopIds); } return false; } }
D'autres exemples sont disponibles dans les modules comptes clients (customer
) ou commandes (order
).
Ce qui est stocké en base dans la propriété de type query
est une représentation JSON de l'ensemble des filtres sélectionnés. Pour exploiter cet ensemble de filtre, on fait appel au service f_persistentdocument_DocumentFilterService
qui dispose des méthodes :
checkValueFromJson($json, $value)
: renvoie un booléen disant si $value
satisfait l'ensemble de filtres décrits par $json
getQueryIntersectionFromJson($json)
: renvoie un instance de f_persistentdocument_criteria_QueryIntersection
ou f_persistentdocument_criteria_QueryUnion
qui sont respectivement une intersection ou une union de plusieurs query. On ne peut pas ajouter de restriction ou appliquer des tris ou des projections à cette instance, comme on peut le faire sur une query. Tout ce que l'on peut faire c'est :add($query)
) : pour ajouter un filtre à cette intersection. Attention, cette query doit impérativement porter sur un modèle compatible avec celui de l'ensemble des query déjà incluses dans l'intersection.find()
) : récupère les instances des documents répondant aux critères de l'ensemble des query de l'intersection.findIds()
) : récupère les identifiants des documents répondant aux critères de l'ensemble des query de l'intersection.Exemple de validation d'un document :
/** * @param customer_persistentdocument_dynamiccustomergroup $group * @param customer_persistentdocument_customer $customer * @return boolean */ protected function doIsMember($group, $customer) { return f_persistentdocument_DocumentFilterService::getInstance()->checkValueFromJson($group->getQuery(), $customer); }
Exemple de requêtage :
/** * @param customer_persistentdocument_dynamiccustomergroup * @return customer_persistentdocument_customer[] */ protected function doGetMembers($group) { $queryIntersection = f_persistentdocument_DocumentFilterService::getInstance()->getQueryIntersectionFromJson($group->getQuery()); return $queryIntersection->find(); } /** * @param customer_persistentdocument_dynamiccustomergroup * @return integer[] */ protected function doGetMemberIds($group) { $queryIntersection = f_persistentdocument_DocumentFilterService::getInstance()->getQueryIntersectionFromJson($group->getQuery()); return $queryIntersection->findIds(); }
Le module filter
propose un document de base pur implémenter des “dossiers intelligents”, c'est à dire des dossiers dont le contenu est dynamique et requêté selon un ensemble de filtres.
Pour réaliser un tel dossier dans un module il faut :
modules_filter/queryfolder
Exemple :
<?xml version="1.0" encoding="utf-8"?> <document xmlns="http://www.rbs.fr/schema/change-document/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.rbs.fr/schema/change-document/1.0 http://www.rbschange.fr/static/schema/change-document/3.5.xsd" model-version="3.5" extend="modules_filter/queryfolder"> </document>
create
et properties
de son éditeur, spécifier l'attribut allow
pour votre modèle de document et les méthodes requises sur les filtresExemple :
<?xml version="1.0" encoding="UTF-8"?> <panel> <section labeli18n="modules.uixul.bo.doceditor.fieldsgroup.Title"> <field name="label" /> <field name="description" /> <field name="query" type="query" allow="catalog/product::getQuery" /> </section> </panel>
child
il faut ajouter l'attribut from
<child model="modules_catalog/simpleproduct" from="function" />
Il est possible de faire des filtres ne portant par sur un document (par exemple sur un panier). Ceci fonctionne globalement de la même manière, avec les spécificités et restrictions suivantes :
getQuery
n'a donc pas de sens dans ce casLa mise en œuvre a aussi quelques spécificités :
objectfilter
au lieu de query
Exemple :
<field name="query" type="objectfilter" allow="order/cart::checkValue" />
getDocumentModelName
du filtreExemple :
/** * @return string */ public static function getDocumentModelName() { return 'order/cart'; }