« Programmation PHP/Exemples/MiniCMS/Développement » : différence entre les versions

Contenu supprimé Contenu ajouté
Ligne 6 :
*Modélisation
 
=== *Implémentation ===
Nous avons besoin des fonctionnalités :
 
<b>TODO</b>
:[1] Complétion des données
:: (none) Create
:: (done) Read
:: (done) Update
:: (done) Delete
:[2] Gestion des users
:[3] Complétion du frameset via templates
:: (done) injection point replacer
:[4] Un webService pour les inputs&query distants
:: (none) common web services
:[5] Parser de snippet
:: (do ) php snippet acter
::: (none) class writer & executer
::: (do ) single actions executer
:[6] Vue xhtml fonctionnelle ajaxifiée
:: (done) xhtml template "nested"
 
 
<b>NICE TO HAVE</b>
:[1.1] Versionning sur données
:: (todo) new crop creating passing current to old
 
 
Le but étant de fournir un maximum de code de service pour les héritages futur le métier n'est approché qu'en vue de personnaliser le CMS client.
 
 
<b>Concernant l'approche modulaire :</b>
 
 
La segmentation en composantes (classes ou scriptlets) permets un découpage net et précis des différentes parties du projets développable séparément les unes des autres. L'association au sein d'un IHM ou couche de présentation via controleur, venant :
 
*avant (pour orienter le développement)
*pendant (pour stabiliser le développement)
*apres (pour finaliser le développement)
 
 
L'interface utilisatrice devant tirer parti des composantes et non l'inverse. La richesse des fonctionnalités est dégagée par le service et enclavé par un code orienté solution client. Il va donc de soi de privilégier proprement, pour enrichir ses propres librairies, de chercher le plus possible à développer ses classes et composantes plutôt que de perdre son temps en métier.
 
 
Concevoir du code de haute qualité signifie souvent d'optimiser le code vers une solution à logique 'customer' càd spécialisée, ce qui n'est pas recherché lorsque l'on doit se développer de l'expérience sur le terrain. Au possible, il faut adapté sa modularité et péricliter le service en business pour le client.
 
 
Concevoir du code modulaire permets de développer concrètement sans trop d'efforts d'analyse pour les développement XP et sur un grand délais. <b>Il est util d'avoir conscience des ces deux approches sur le terrain ou lorsque l'on décide d'ouvrir son code au monde. La majorité des développeur ne faisant trop souvent qu'intégrer pour l'argent ou la renommée chez le client le fruit du travail des autres</b>.
 
 
<b>'Classes & Fermes de fonctions' contre 'scriptlets'</b>
 
Les premières sont :
 
 
:plus simple à coder
:plus simple à utiliser
:plus simple à entretenir
:plus simple à spécialiser
:plus facilement réutilisable
:plus orienté objet
:plus susceptible d'être récupéré
 
 
les secondes sont :
 
 
:plus criptiques
:plus optimisée pour le client
:aisément traductible en classes et fermes
:plus fonctionnelle
:plus procédurales
:plus chaotiques (*)
 
 
<small>* produire un code obscure voir chaotique présente de nombreux avantages. D'autres analystes et collègues y perdront leur temps d'une part, les piping inhérents au code permettant l'implémentation, le hacking, le reroutage et la divergence fonctionnelle de script. Ce qui n'est pas sans avantages.</small>
 
 
==== [1] Complétion des données ====
Pour disposer des fonctionnalités de gestion de données.
* On a besoin de Getter/Setter de données :
: - depuis la vue
: - depuis le webservice
: - depuis l'URL
* Pour les fichiers xml
 
 
Les fonctionnalités d'appels sont regroupées ci-dessous
 
<source lang="php">
<?php
 
class dataManager
{//
private $pattern;
 
protected $stack, $domDoc;
//
public static
$crop
= array(
"std"=> '<?xml version="1.0" encoding="UTF-8"?>
<crop><topic /><fields /></crop>'
,""=>''
),
$data
= array(
"root"=>"/miniCMS/Owners/"
);
# TODO GETTER/SETTER
public static function getContentByData($pData)
/**
* get the content from data
*/
{}
public static function setContentByData($pData)
/**
* set the content to data
*/
{}
public function setCropForCustomer($pPath, $pCrop)
/**
* set crop to customer directory
*/
{
file_put_contents($pPath, $pCrop);
}
# STATE
public function dataManager()
{
$this->initialize();
}
public function initialize()
/**
* init
*/
{
$this->domDoc = new DOMDocument();
if(@$this->data['path'])
{
$this->domDoc->load( $this->data['path'] );
}
return $this->domDoc;
}
public function saveDocument()
/**
* save document
*/
{
$this->domDoc->save($this->data['path']) ;
}
}
 
?>
</source>
 
 
On implémente ces fonctions utilitaires à utilities.class.php
 
<source lang="php">
 
public static function searchNodesByContent($pDocument, $pQueries)
/**
* return node matching request
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
 
/* ceci est un exemple de mauvais code de service,
il ne fonctionne que pour un nombre limité de structure de crop
c'est donc un code métier à surcharger. */
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node']
&& ! utilities::isContent ( $pQueries['value']
, $v->nodeValue ) )
$_fields->item(0)->removeChild($u) ;
}
}
/* DEPRECIEE - Malheureusement, ce code ne marche pas une fois appelée par getNodes
for($i=0; $i<count($pNodeSet); $i++)
{
$_content = $pNodeSet[$i]->childNodes ;
// cette fonction ne descend qu'à un niveau
foreach($_content as $v)
{
if( $v->nodeName == $pQueries['node']
&& ! utilities::isContent ( $pQueries['value']
, $v->nodeValue ) )
{
$pNodeSet[$i]->parentNode->removeChild($pNodeSet[$i]) ;
}
}
}
*/
 
return $pDocument ;
}
 
public static function searchNodesByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
if( !($u->getAttribute($pQueries['attribute'])
== $pQueries['value']) ) // 1:1 match
$_fields->item(0)->removeChild($u) ;
}
 
return $pDocument ;
}
 
</source>
 
 
On implémente la méthode getContentByData du dataManager
 
<source lang="php">
 
public static function getContentByData($pDocument, $pData)
/**
* get the content from data
*/
{
 
/* DEPRECIEE - code pour l'appel à getNodes déprécié pour searchNodes
if($pData['node'])
{
if($pData['node'] != "*")
{
$expr = '//'.$pData['node'].'/..' ;
} else {
return $pDocument ;
}
} else {
return false ;
}
*/
 
return utilities::searchNodes($pDocument, $pData) ;
}
 
# Qui devient ici capable de distinguer
# recherche de contenu d'avec les attributs de noeuds
 
public static function getContentByData($pDocument, $pData)
/**
* get the content from data
*/
{
if(@$pData['attribute'])
{
return utilities::searchNodesByAttribute($pDocument, $pData) ;
}
else
{
return utilities::searchNodesByContent($pDocument, $pData) ;
}
}
 
</source>
 
 
Un test du getContentByData nous rend nominal pour cette phase dans cette modélisation métier.
Le refactoring du business en services serait apréciée...
 
 
<source lang="php">
 
<?php
 
require_once "./Cms/Classes/dataManager.class.php";
require_once "./Cms/Classes/utilities.class.php";
 
//echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
 
$_manager = new dataManager();
 
//
$_manager->data['path'] = $_manager->data['root'] . "Customer1/Contents/crop1.xml"; //
 
// init manager
$_document = $_manager->initialize();
 
# QUERY TESTS
// setting de valeur a récuperer
$_manager->data['query_1'] = array("node"=>"subject","value"=>"potager");
$_manager->data['query_2'] = array("node"=>"content","value"=>"molle2");
// queries test
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1'])
,$_manager->data['query_22']);
 
# OUTPUT
echo $res->saveXML();
 
?>
 
</source>
 
 
: -> Sur l'input crop1.xml
 
<source lang="xml">
 
<?xml version="1.0" encoding="UTF-8"?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_01">
<subject>les légumes du potager</subject>
<content>le potagé contient une terre molle.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
<field id="k_02">
<subject>les lésgumes du test</subject>
<content>le potagé contient une terre molle.
Parsemé ça et là, on Test carottes,
petits poids et potirons</content>
</field>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>le potagé contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons</content>
</field>
</fields>
</crop>
 
</source>
 
 
: -> On à pour résultat :
 
<source lang="xml">
 
<?xml version="1.0" encoding="UTF-8" ?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>le potagé contient une terre molle2.
Parsemé ça et là, on trouve carottes, petits poids et potirons
</content>
</field>
</fields>
</crop>
 
</source>
 
 
Le deuxieme test de get par l'id est egalement concluant
 
<source lang="php">
 
# QUERY TESTS
// setting de valeur a récuperer
$_manager->data['query_1'] = array("node"=>"subject", "value"=>"potager");
$_manager->data['query_2'] = array("attribute"=>"id", "value"=>"k_03");
// query test
$res = dataManager::getContentByData(
dataManager::getContentByData($_document, $_manager->data['query_1'])
,$_manager->data['query_2']);
# OUTPUT
echo $res->saveXML();
 
</source>
 
 
Son résultat étant :
 
<source lang="xml">
 
<?xml version="1.0" encoding="UTF-8" ?>
<crop>
<topic>Cultiver son potager</topic>
<scope>légumes potager soupe</scope>
<fields>
<field id="k_03">
<subject>les légumes du potager</subject>
<content>le potagé contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
</fields>
</crop>
 
</source>
 
 
On implémente à utilities.class.php les méthodes :
 
<source lang="php">
 
public static function writeContentByAttribute($pDocument, $pQueries)
/**
* write node content matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
if( $u->getAttribute($pQueries['attribute'])
== $pQueries['value'] ) // 1:1 match
{
$v = $u->getElementsByTagName($pQueries['node']) ;
$v->item(0)->nodeValue = $pQueries['replacement'] ;
$_flg = true ;
}
}
}
return $_flg ;
}
public static function deleteContentByContent($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
foreach ( $u->childNodes as $v )
{
if( $v->nodeName == $pQueries['node']
&& utilities::isContent ( $pQueries['value'] ,
$v->nodeValue ) )
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
}
}
return $_flg ;
}
 
public static function deleteContentByAttribute($pDocument, $pQueries)
/**
* delete node matching request by attribute
*/
{
$_fields = $pDocument->getElementsByTagName('fields');
$_flg = false ;
foreach ($_fields->item(0)->childNodes as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
if( $u->getAttribute($pQueries['attribute'])
== $pQueries['value'] ) // 1:1 match
{
$_fields->item(0)->removeChild($u) ;
$_flg = true ;
}
}
return $_flg ;
}
</source>
 
 
et à la classe dataManager.class.php
<source lang="php">
 
public function setContentByData($pDocument,$pData)
/**
* set the content to data
*/
{
if(@$pData['attribute'])
{
utilities::writeContentByAttribute($pDocument, $pData) ;
}
else
{
utilities::writeContentByContent($pDocument, $pData) ;
}
$this->saveDocument();
}
 
</source>
 
 
Le test suivant permet de valider la phase d'écriture :
 
<source lang="php">
 
# QUERY TESTS
// setting de valeur a récuperer
$_manager->data['query_1'] = array("attribute"=>"id", "value"=>"k_03","node"=>"subject",
"replacement"=>"this is the new subject");
// query test
$_manager->setContentByData($_document, $_manager->data['query_1']);
 
</source>
 
 
avec le resultat
 
<source lang="xml">
 
<field id="k_03">
<subject>this is the new subject</subject>
<content>le potagé contient une terre molle2.
Parsemé ça et là, on trouve carottes,
petits poids et potirons
</content>
</field>
 
</source>
===== Liste des défauts =====
* searchContents / getContentByData est du code métier
: -> le transcrire en code de service
* pour la suppression, delete reste à implémenter correctement
: -> à dataManager
 
==== [2] Gestion des utilisateurs ====
La gestion des users suivant le modèle ne demande que l'extraction de la ligne utilisateur pour recueillir ses infos.
 
 
Dans la classe utilities.class.php on rajoute ces deux méthodes :
 
<source lang="php">
 
public static function getAttributesContents($pNode)
/**
* retourne les attributs dans un tableau
*/
{
foreach ($pNode->attributes as $attrName => $attrNode)
{
$_tab[$attrName] = $attrNode->value ;
}
return $_tab ;
}
public static function getLineByAttribute($pDocument, $pQueries)
/**
* return node matching request by attribute
*/
{
$_users = $pDocument->getElementsByTagName('user');
foreach ($_users as $u)
{
if ($u->nodeType != XML_TEXT_NODE)
{
$_flg = true ;
foreach($pQueries as $_qa )
{
if( !($u->getAttribute($_qa['attribute'])
== $_qa['value']) || !$_flg )
{ // 1:1 match
$_flg = false ;
}
}
if($_flg)
return utilities::getAttributesContents($u) ;
}
}
return false ;
}
 
</source>
 
 
Un test concluant de ces méthodes nous rend opérationnel pour cette phase
 
<source lang="php">
<?php
 
require_once "./Cms/Classes/dataManager.class.php";
require_once "./Cms/Classes/utilities.class.php";
 
$_manager = new dataManager();
 
//
$_manager->data['path'] = $_manager->data['root'] . "Customer1/Contents/user1.xml"; //
 
# QUERY TESTS
$_manager->data['query_1'] = array(
array("attribute"=>"login", "value"=>"user2")
, array("attribute"=>"pass", "value"=>"pass2"));
// query test
$_res = utilities::getLineByAttribute($_manager->initialize(), $_manager->data['query_1']) ;
# OUTPUT
print_r_html($_res);
 
?>
</source>
 
 
Résultat :
<pre>
Array
(
[login] => user2
[pass] => pass2
[lastname] => name2
[forname] => forName2
[rights] => u
[customer] => customer2
)
</pre>
===== Liste des défauts =====
Cette gestion des users ne prend actuellement que les attributs de noeuds...
:-> il serait util de pourvoir des données supplémentaires dans le noeud user
 
==== [3] Complétion du frameset ====
* Pour commencer nous déclarons une classe utilities.class.php qui reprendra toutes les fonctionnalités utiles pour notre solution
 
 
<source lang="php">
 
<?php
 
function print_r_html($data,$return_data=false)
{
$data = print_r($data,true);
$data = str_replace( " ","&nbsp;", $data);
$data = str_replace( "\r\n","<br/>\r\n", $data);
$data = str_replace( "\r","<br/>\r", $data);
$data = str_replace( "\n","<br/>\n", $data);
if (!$return_data)
echo $data;
else
return $data;
}
 
class utilities
{
public static function getNodes($pDomDoc, $pXpathString)
/**
* get the node from DomDoc with XpathString
*/
{
$xp = new DOMXPath($pDomDoc);
$xp->registerNamespace('x', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
$xp->registerNamespace('i18n', 'http://apache.org/cocoon/i18n/2.1');
$ret = array();
$nodes = $xp->query($pXpathString);
foreach ($nodes as $node)
{
array_push($ret, $node);
}
}
public static function isContent($pPattern, $pFeed)
/**
* check is pattern in feed
*/
{
return preg_match('/('.$pPattern.')/',$pFeed)?true:false;
}
 
}
 
?>
 
</source>
 
* La classe templateControler regroupe les fonctions sur les templates. Le contenu à publier est segmenté en flux.
* Son objectif est de :
: - reformatter
: - assembler
: - publier
 
<source lang="php">
<?php
 
class templateControler
{
/**
* Control the templates
*
* @var (mixed) ($domDoc, $data, $stack)
*/
//
private $pattern
= array(
'inc'=>'/{{inc:[a-zA-Z]*[\[\]\*]*}}/' // inclusion patterns
,''=>''
);
protected $domDoc, $stack;
//
public $data;
/*
public function __construct()
{//
$this->domDoc = new DOMDocument();
$this->initialize();
}
*/
/**
* Enter description here...
*
* @param unknown_type $pVar
* @return templateControler
*/
public function templateControler($pVar)
{
$this->domDoc = new DOMDocument();
$this->setter($pVar);
$this->initialize();
}
/**
* Enter description here...
*
* @param unknown_type $pVar
*/
public function setter($pVar)
{//
$this->data = $pVar;
}
// TODO GETTER
public function initialize()
{//
$this->domDoc->load( $this->data['path'] );
}
/**
* Enter description here...
*
*/
public function getNode($pXPath) //"//html/body/*[@class='text']";
{//
return $this->data['feed'] = utilities::getNodes($this->domDoc, $pXPath);
}
 
public function getAnchors($pFeed)
{//
preg_match_all($this->pattern['inc'], $pFeed, $this->data['result']);
return $this->data['result']; // on prefere les données centralisées dans la classe
}
public function publicTemplate()
{//
return $this->domDoc->saveXML();
}
}
 
</source>
 
Le test suivant permet de voir si on prend bien les ancres d'injection, par exemple, pour le noeud frameset/content
 
<source lang="php">
//
$tmp = new templateControler(array("path"=>"object.xml"));
$tst = $tmp->getNode("//template/frameset/content");
 
print_r_html($tmp->getAnchors($tst[0]->nodeValue));
</source>
<pre>
Array
(
[0] => Array
(
[0] => {{inc:styles[*]}}
[1] => {{inc:scripts[*]}}
[2] => {{inc:bodies[*]}}
)
 
)
</pre>
 
On implémente ces deux nouvelles fonctions à templateControler
 
<source lang="php">
/**
* getContent retourne le contenu du/des noeud demandés
*
* > (nodeName, nodeId) - (string) >
*/
public function getContent($nodeName,$nodeId)
{//
// set expression
$_expr = "//" . $nodeName . ($nodeId&&$nodeId!='*'?"[id='$nodeId']":"");
$set = $this->getNode($_expr);
$content=null;
foreach($set as $k=>$v)
{
$content .= "<" . $nodeName . ">" . trim($v->nodeValue) . "</" . $nodeName . ">";
}
return $content?$content:"";
}
/**
* replace anchor by content
*/
public function anchorContentReplacer()
{//
$i=0;
foreach($this->data['result'][0] as $k=>$v)
{
// formatting anchor's syllabes
$v = str_replace(array('{','[','}',']'),array('',' ','',''),$v);
$tmp = explode(":",$v);$_ = explode(' ',$tmp[1]);
// replacing
$this->data['result'][0][$i++]
= array("node"=>$_[0],"value"=>$this->getContent($_[0], $_[1]));
}
}
// TODO : getNodeById pour disposer d'un getElementById moins spécialisé et fonctionnel ici.
 
</source>
 
Le test siuvant permet de voir si on remplace bien l'ancre par son contenu :
 
<source lang="php">
require_once "templateControler.class.php";
 
//
$tmp = new templateControler(array("path"=>"template.xml"));
 
$tmp->getNode("//template/frameset/content");
 
$tmp->getAnchors(
$tmp
->data['feed'][0]
->nodeValue);
 
$tmp->anchorContentReplacer();
 
print_r_html(
$tmp->data['result']
);
</source>
 
<pre>
Array
(
[0] => Array
(
[0] => Array
(
[node] => styles
[value] =>
body
{
background-color:red;
font-family:verdana;
padding:10px;
}
.style_01
{
/* some style in */
}
)
 
[1] => Array
(
[node] => scripts
[value] =>
)
 
[2] => Array
(
[node] => bodies
[value] =>
)
 
)
 
)
 
</pre>
 
Le contenu est bien localisé dans le template, par conséquent l'objectif de cette phase est atteind. On est opérationnel pour l'injection.
 
 
* La classe snippetActer regroupe les fonctions de computation des snippets
 
<source lang="php">
class SnippetActer
{//
protected $_type,$_content,$_stack;
# GET/SET
public function setType()
{//
list($_1,$_2)=explode('/',$this->_type);
$this->actCode(array($_1,$_2));
}
public function setContent($pContent)
{//
$this->_content = $pContent;
}
# STATE
 
/*
* act statements
*/
public function actCode($pType)
{//
switch(true)
{
case $pType[0]=="text"&&$pType[0]=="php":
//
$this->write();
break;
case $pType[0]=="text"&&$pType[0]=="javascript":
$this->inject();
//
break;
case $pType[0]=="action"&&$pType[0]=="php":
$this->unstack();$this->act();
break;
}
}
/*
* stack statements
*/
public function unstack()
{//
$_this->_stack = explode(";",$this->_content);
}
/*
* execute statements
*/
public function act()
{//
foreach($this->_stack as $v)
eval($v);
}
 
}
</source>
 
===== Liste des défauts =====
* Les snippets actifs à la volée peuvent ralentir l'output
: -> l'ajaxification peut fournir le contenu en deux temps (données avant/après traitements)
* Cette classe reste à faire...
 
==== [4] Un [[../webService|webService]] ====
Le webservice propose par défaut une synergie avec d'autres solutions, sites, applications. Des fonctionnalités distantes permettent l'interfaçage logicielle avec le serveur de données, option qui deviendra basique pour les systèmes orientés données.
<source lang="php">
<?php
/*
webservice servker
*/
# no chache in testing
ini_set("soap.wsdl_cache_enabled", "0");
# wsdl
$serveurSOAP = new SoapServer('service.wsdl');
# provided services
 
/* inqueries services
in :
out :
*/
$serveurSOAP->addFunction('request');
// ...
/* high level services
in :
out :
*/
$serveurSOAP->addFunction('harvest');
// ...
/* low level services
in :
out :
*/
$serveurSOAP->addFunction('deploy');
$serveurSOAP->addFunction('f_01');
$serveurSOAP->addFunction('f_02');
// ...
# server start
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$serveurSOAP->handle();
}
# services integration
require_once "./Services/request.srv.php";
require_once "./Services/service_01.srv.php";
require_once "./Services/service_02.srv.php";
# online status checker
function listen($pIn)
{
return 'Copy : ' . $pIn;
}
?>
</source>
 
===== Liste des défauts =====
 
=== Déploiement de la vue ===