NairuS

Aller au contenu | Aller au menu | Aller à la recherche

dimanche, janvier 10 2010

Personnaliser les logs dans Symfony

Il y a quelque temps j'ai été confronté à une problématique de gestion des logs dans mon application Symfony. Face à des retours d'utilisateurs de plus en plus nombreux, notre client nous a demandé de tracer les problèmes sur les différentes actions du site.

Le problème était que je voulais stocker les logs dans des fichiers séparés selon les actions symfony. La classe sfAggregateLogger permet de définir plusieurs fichiers de log mais il n'y a avait aucun moyen de diffuser un log uniquement sur un fichier de cette classe. J'ai donc créé une class qui hérite de cette classe: MultiLoggerCollection

<?php
/**
 * Manages the Multi loggers of the application
 *
 * @package lib.core
 * @subpackage logging
 *
 * @author nsurian
 *
 */

class MultiLoggerCollection extends sfAggregateLogger
{
    /**
     * Logs a alert message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function alertM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::ALERT ) ;
    }

    /**
     * Logs a critical message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function criticalM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::CRIT ) ;
    }

    /**
     * Logs a debug message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function debugM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::DEBUG ) ;
    }

    /**
     * Logs a emerg message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function emergM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::EMERG ) ;
    }

    /**
     * Logs a error message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function errM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::ERR ) ;
    }

    /**
     * Returns the logger if exists.
     * @param String $name
     * @return mixed Logger if exists or NULL
     */

    public function getLogger( $name )
    {
        $loggers = $this->getLoggers() ;
        foreach( $loggers as $logger )
        {
            if( $logger instanceof MultiLogger && $logger->name === $name )
            {
                return $logger;
            }
        }
        $this->log( 'The instance of the logger "' . $name . '" does not exist!', sfLogger::ERR ) ;
        return null ;
    }

    /**
     * Logs a info message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function infoM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::INFO );
    }

    /**
     * Logs a notice message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function noticeM( $message, $name )
    {
        $this->doLogM( $message, $name, sfLogger::NOTICE );
    }

    /**
     * Logs a warning message.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     */

    public function warningM( $message, $name )
    {         $this->doLogM( $message, $name, sfLogger::WARNING );
    }

    /**
     * Dispatch the message to the right log file.
     *
     * @param string $message The message to write in the log file.
     * @param string $name The name of the logger where dispatch the message.
     * @param integer $priority The number of the log priority
     * @return void
     */

    protected function doLogM( $message, $name, $priority )
    {
        $logger = $this->getLogger( $name ) ;
        if( $logger !== null )
        {
            $logger->log( $message . $this->getUserAgent(), $priority ) ;
        }
        else
        {
            $this->log( '[' . $name . ']' . $message . $this->getUserAgent(), $priority );
        }
    }

    /**
     * Return the user agent server info.
     *
     * @return string
     */

    protected function getUserAgent()
    {
        return ' [ USER AGENT: ' . $_SERVER['HTTP_USER_AGENT'] . '; IP: ' . $_SERVER['REMOTE_ADDR'] . ' ] ';
    } }

?>

Chose étrange, je n'ai pas pu surcharger les méthodes de la classe mère pour les adapter à mon model. Symfony me remonte une erreur PHP dans la signature des méthodes.
J'ai donc réécrit les méthodes en leur mettant le suffixe 'M' pour 'MultiLogger'.

Explication de la méthode principale: MultiLogger::doLogM( $message, $name, $priority )
Le paramètre $name permet de récupérer le logger avec la méthode MultiLogger::getLogger( $name ).
Si cette méthode renvoie le logger demandé, alors on peut dispatcher le message vers le bon fichier de log sinon on dispatche vers le fichier principal avec le flag [name] en plus.

La méthode MultiLogger::getLogger( $name ) permet de parcourir la collection de logger et de renvoyé celui recherché s'il existe.
Pour ce faire j'ai étendu la class sfFileLogger comme ceci:

<?php
/**
 * Customize the loggers of the application
 *
 * @package lib.core
 * @subpackage logging
 *
 * @author nsurian
 *
 */

class MultiLogger extends sfFileLogger
{
    /**
     * Defines the name of the logger.
     * @var String
     */
    public $name ;

    /**
     * Initializes this logger.
     *
     * Available options:
     *
     * - name: The name of the logger.
     *
     * @param sfEventDispatcher $dispatcher A sfEventDispatcher instance
     * @param array $options An array of options.
     *
     * @return Boolean true, if initialization completes successfully, otherwise false.
     */

    public function initialize( sfEventDispatcher $dispatcher, $options = array() )
    {
        parent::initialize( $dispatcher, $options ) ;

        if( isset( $options['name'] ) )
        {
            $this->name = $options['name'];
        }
    }
} ?>

Ensuite j'ai personnalisé le fichier factories.yml du répertoire apps/frontend/config:

prod:
  logger:
    class: MultiLoggerCollection
    param:
      level: emerg
      loggers:
        login_notice:
          class: MultiLogger
          param:
           name: login
            level: notice
            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%_login.log
        recover_passwd_notice:
          class: MultiLogger
          param:
            name: recover_passwd
            level: notice
            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%_recover_passwd.log
        signin_notice:
          class: MultiLogger
          param:
            name: signin
            level: notice
            file: %SF_LOG_DIR%/%SF_APP%_%SF_ENVIRONMENT%_signin.log

Puis j'appelle les loggers qui m'intéressent dans les actions comme ceci:

public class mainActions extends sfActions
{
    public function executeLogin( sfWebRequest $request )
    {
        $login = $request->getParameter( 'login' ) ;
        $pass = $request->getParameter( 'pass' ) ;

        // Check to login in the database.
        if( User::login( $login, $pass ) === false )
        {
            // Write errors in the form.
            $this->message = <<<EOF
Login failed with the login '$login' and the pass '$pass'
EOF;
            // Write the error in log file if logging enabled.
            if( sfConfig::get( 'sf_logging_enabled' ) )
            {
                 $logger = $this->getLogger() ;
                $logger->noticeM( $this->message, MultiLoggerList::LOGIN ) ;
            }
        }
    }
}

Pour plus de souplesse et de sécurité, j'ai ajouté une classe d'énumération des loggers dans la package logging:

<?php
/**
 * Customize the loggers of the application
 *
 * @package lib.core
 * @subpackage logging
 *
 * @author nsurian
 *
 */

class MultiLoggerList
{
    /**
     * Defines the name of the logger for the login.
     * @var String
     */

    const LOGIN = "login" ;

    /**
     * Defines the name of the logger for the recover password.
     * @var String
     */

    const RECOVER_PASSWD = "recover_passwd" ;

    /**
     * Defines the name of the logger for the signin.
     * @var String
     */

    const SIGNIN = "signin" ;
}
?>

Il est très facile de rajouter un fichier de log par la suite.


Enfin il faut s'assurer que les logs sont activés dans le fichiers apps/frontend/config/settings.yml:

prod:
  .settings:
    no_script_name: on
    logging_enabled: on
    error_reporting: <?php echo (E_PARSE | E_COMPILE_ERROR | E_ERROR | E_CORE_ERROR | E_USER_ERROR)."\n" ?>

Et voilà, il me reste plus qu'a configurer la rotation des logs en ligne de commande et le tour et joué.

php symfony log:rotate frontend prod --period=7 --history=10

Pour plus de détail la documentation symfony est très complète: http://www.symfony-project.org/book/1_2/16-Application-Management-Tools

Enjoy ;)

lundi, décembre 21 2009

La class sfYaml

Ce tuto va vous expliquer comment utiliser cette class qui peut être forte utile.

Dans un de mes derniers projets je devais mettre à jour une correspondance de données entre 2 différentes sources.

Je recevais d'un côté un identifiant envoyé par un script externe en GET et je devais faire une connexion SOAP à une autre BDD pour finir l'action demandée par l'utilisateur.

Etant donné que je n'avais pas de BDD côté symfony, j'ai commencé par ajouter les données envoyées dans le fichier app.yml du front.

Puis j'ai créé une class utilitaire pour faire la correspondance.

Cette technique présentait 2 défauts:
1- Lors de mise à jour des données envoyées, il faut modifier la class utilitaire et le fichier app.yml
2- Les tests unitaires de la méthode sont impossibles car elle faisait appel au fichier app.yml via la méthode sfConfig::get() et ce fichier n'était pas mis en cache.

J'ai donc trouvé une autre technique bien meilleures à mon gout.

J'ai commencé par créer mon fichier de correspondance items.yml dans le répertoire /config/updates à la racine du projet.

1: { id: I002, label: "Shoes blue", slug: shoes_blue }
2: { id: I003, label: Recycle, slug: recycle }
3: { id: I004, label: Tables, slug: tables }
4: { id: I006, label: "Shoes red", slug: shoes_red }
5: { id: I008, label: Chairs, slug: chairs }

Ensuite j'ai créé cette méthode dans la class utilitaire:

/**
 * Matches the items IDs
 * @param String $itemID
 * @return String of the item ID matched.
 */
public static function matchItemID( $itemID )
{
    $items = sfYaml::load( sfConfig::get( 'sf_config_dir' ) . "/updates/items.yml" ) ;
    return key_exists( $itemID, $items )     
    ? $items[$itemID]['id']     
    : '' ;
}

Puis j'ai fait ces tests unitaires:

$t->comment( "::matchItemID" ) ;
$t->is( Utils::matchItemID( 4 ) , "I006" , "The itemID must matched to 'I006'" ) ;
$t->is( Utils::matchItemID( "" ) , "" , "The itemID must be an empty string" ) ;

Une fois certain que ma correspondance fonctionnait, je l'ai mise en place dans son action dédiée:

public function executeMatchItem( sfWebRequest $request )
{
    $itemID = $request->getPostParameter( 'item_id' ) ;
    $item = Utils::matchItemID( $itemID );
...
}

Et voilà le tour est joué.

Maintenant je n'ai plus qu'à ajouter les entrées dans mon fichier items.yml quand il faut.

mercredi, octobre 7 2009

Astuces pour des projets à configuration multiple

Lorsque l'on travaille à plusieurs sur un projet avec le SVN, on a souvent des conflits de configurations à cause de chaque environnements de développement.
Plutôt que de réfléchir à avoir chez chaque acteur le même environnement, je vais tenter de proposer une solution pour en avoir une par environnement.

Quelles sont, en générale, les configurations identique que l'on retrouve dans chaque environnement?
 1) La configuration de la Base de Données
 2) Les librairies, framework externe aux projets
Prenons un projet PHP de base dont une exemple existe dans le projet open source Ast'r.
Nous allons créer un fichier 'default_mysql_conf.properties' qui sera envoyé dans le dépôt SVN.
Dans ce fichier on écrit ceci:

host=localhost
name=egallery
user=root
pass=

Ce seront les paramètres par défaut de la BDD.
Avec ce fichier nous allons créer le fichier MysqlConfig.php qui va gérer la configuration de la BDD.

<?php
    /**
     * Defines the configuration of Mysql.
     */

    class MysqlConfig
    {
        /**
         * Creates a MysqlConfig
         * @param String $file The customize file path for mysql connection.
         */

        public function MysqlConfig( $file = null )
        {
            $this->file = null ;
          
            // Tests if the custom file exists
            if( file_exists( $file ) )
            {
                $this->file = $file ;
            }
            else
            {
                // Tests if the mysql config file exists.
                if( file_exists( self::MYSQL_CONFIG ) )
                {
                    $this->file = self::MYSQL_CONFIG ;
                }
                else
                {
                    if( file_exists( self::DEFAULT_MYSQL_CONFIG ) )
                    {
                        $this->file = self::DEFAULT_MYSQL_CONFIG ;
                    }
                }
            } 
            
            if( $this->file != null )
            {
                try
                {
                    $handle = fopen( $this->file , "r" ) ;
                    while( !feof( $handle ) )
                    {
                        $page        = fgets( $handle , 4096 ) 
                        $page        = explode( '=' , trim( $page ) ) ;
                        $prop        = trim( $page[0] ) ;
                        $value       = trim( $page[1] ) ;
                        $this->$prop = $value ;
                    }
                    fclose( $handle ) ;
                }
                catch( Exception $e )
                {
                    throw  new Exception( "The mysql config file doesn't exist!" ) ;
                }
            }
            else
            {
                throw new Exception( "The mysql config file doesn't exist!" ) ;
            }
        }
                

        /**
         * The default path of default_mysql.properties file.
         * @var String
         */
        const DEFAULT_MYSQL_CONFIG = "../config/default_mysql.properties" ;
        
        /**
         * The default path of mysql.properties.
         * @var String
         */
        const MYSQL_CONFIG = "../config/mysql.properties" ;
        
        /**
         * The name of the config file.
         * @var String
         */
        public $file ;
        
        /**
         * The db host.
         * @var String
         */ 
        public $host ;
            
        /**
         * The db name.
         * @var String
         */
        public $name ;
        
        /**
         * The db user password.
         * @var String
         */ 
        public $pass ;
        
        /**
         * The db user name.
         * @var String
         */ 
        public $user ;

    }
?>

Pour personnalisé la configuration de la BDD il suffit :
 - De créer un fichier mysql.properties
 - De l'ignorer sur le svn:

# lancer la commande, en partant du principe qu'on se situe en dessous du répertoire config.
svn propedit svn:ignore config/

# ecrire le nom des fichiers, les extensions ( *.ext ) ou les répertoire ( myFolder/ )
mysql.properties

 
 - D'y mettre ses propres valeurs.
Ex. pour MAMP:
host=localhost:8889
name=egallery
user=root
pass=root
Note: La class MysqlConfig permet également de personnalisé le nom du fichier de configuration.
Pour ce servir de la class MysqlConfig il suffit de l'instancier dans le code et envoyer ses propriétés pour se connecter à la BDD.
CF. README.txt
Depuis plusieurs mois je travaille avec le framework symfony.
Voilà comment je le configurerais non seulement pour le travail d'équipe mais également pour le déploiement de plusieurs projets symfony sur le même serveur avec un seul framework à maintenir.
Pour ce connecter à la Base de données, l'ORM de symfony (propel ou doctrine) a besoin du fichier config/database.yml.
J'ignore ce dernier sur le svn et je crée un fichier config/database.yml.temp que j'envoie dans le dépôt svn avec la configuration de base.

Ainsi, quelle que soit l'environnement de développement on peut avoir une configuration de base de données personnalisée.
De la même manière on peut imaginer personnalisé le chargement du framework symfony au sein des projets.
En effet, il est quand même plus pratique de n'avoir qu'un seul noyau des versions du framework sur un serveur qui héberge plusieurs projets symfony.
Admettons que sur mon windows je place symfony dans un répertoire c:\libs\symfony-1.2.9, sur unix (Mac ou Linux) je le stocke dans un répertoire /libs/symfony-1.2.9
Dans le répertoire config/ de chaque projet symfony je crée un fichier core.properties.default dans lequel j'écris ceci:

<?php
    $sfLibsPath = "/libs/symfony-1.2.9" ;
?>

J'envoie ce fichier sur le svn et je le copie avec le nom core.properties que j'ignore du svn pour pouvoir changer la config selon les environnements.
Dans le fichier config/ProjectConfiguration.class.php j'écris ceci:

<?php
$coreFile =
dirname( __FILE__ ) . '/core.properties' ;
if( $coreFile && filesize( $coreFile ) > 0 )
{
  require_once $coreFile ;
}
else
{
  require_once $coreFile . '.default' ;
}
require_once $sfLibsPath . '/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // for compatibility / remove and enable only the plugins you want
    $this->enableAllPluginsExcept( array( 'sfPropelPlugin' , 'sfCompat10Plugin' ) ) ;
  }
}
?>

Grâce à la classe sfCoreAutoload de symfony, je n'ai plus besoin de définir les imports de fichiers dans mes nouvelles classes.
Donc si je viens à travailler sous windows je n'aurais qu'à créer le fichier core.properties.temp dans le répertoire config/ de mon projet de d'écrire ceci:

<?php
    $sfLibsPath = "c:\libs\symfony-1.2.9" ;
?>

Note: Mes fichiers de configuration n'ont pas d'extension .php pour mieux les différencier des fichiers classiques. 
  Pour PHP c'est la même chose à partir du moment où on fait un include ou un require et que le script est bien écrit avec les balises <?php ?>

samedi, septembre 19 2009

Faire de l'AMF Remoting avec Symfony

Hello,

Ces derniers temps j'ai pas mal eu de bouleaux en Symfony essentiellement et je suis content d'avoir pu terminé ce tutoriel.

Il s'adresse essentiellement aux développeurs symfony ou à ceux qui souhaitent s'y mettre. Des notions d'AS3 et de pattern MVC sont également importantes à avoir.

Bref, vous l'aurez compris, c'est un tutoriel pour développeurs expérimentés.


Pour cette démonstration j'utiliserai le framework applicatif de Vegas AST'r avec son exemple eGallery pour la partie ActionScript.

Ce tutoriel est réalisable sur tout support (Linux, MAC, Windows).
Je tâcherais de données les tips pour ces 3 supports.

Je passerai les détails de l'utilisation du SVN mais personnellement je travaille toujours avec dans la mesure du possible.
Je donnerai les conseils principaux concernant la structure symfony.
Pour les débutant en symfony, je vous conseille fortement de vous familiariser avec le framework en suivant ce tutorial.

Avant de commencer, assurez vous que votre machine possède tous les outils nécessaires:
 - Un serveur Apache2 avec PHP 5.2.4 minimum (MAMP, WAMP, LAMP).
 - La librairie PEAR pour PHP installée.
 - La commande php doit être lancée depuis le terminal.
 - Pour les utilisateurs Windows il faut ajouter le fichier php.exe de WAMP dans les variables d'environnement.
 - Pour les utilisateurs MAC il faut s'assurer que c'est bien le bon php qui est exécuté en tapant la commande : php -v
 - Si ce n'est pas le cas il faut editer le fichier .bash_profile dans votre répertoire utilisateur et ajouter la ligne:

# edit the .bash_profile file
mac:~$ nano
.bash_profile

# add this line
PATH=/Applications/MAMP/bin/php5/bin:$PATH export PATH

 - Avoir un client svn pour récupérer les derniers dépôts des libraries opensources.
Pour les utilisateurs de WAMP, il est probable que PEAR ne soit pas installé.
Rien de plus simple, dans le répertoire PHP de WAMP il y a un fichier go-pear.bat qu'il suffit de lancer via le terminal.

I/ Créer le projet symfony.

1) Première étape, comme dans tout projet symfony, créer l'environnement du projet.

 - Ouvrez un terminal.

 - Créer le répertoire du projet et celui de la librarie symfony :

## MAC / LINUX:
  mkdir -p /projects/egallery/trunk/deploy/lib/vendor
 
## WINDOWS:
  mkdir c:\projects\egallery\trunk\deploy\lib\vendor

  - Aller dans le repertoire lib/vendor.

  - Exporter le dernier tags du projets symfony:

 Sur windows utiliser TortoiseSVN ou installer VisualSVN et créer la variable d'environnement sur le fichier svn.exe // TODO finish explain

 svn export http://svn.symfony-project.com/tags/RELEASE_1_2_8/ symfony/

- Revenir à la racine du projet :

cd ../..

- Vérifier la configuration du système:

php lib/vendor/symfony/data/bin/check_configuration.php


Le script renvoie ceci quand tout va bien:

********************************
*                              *
*  symfony requirements check  *
*                              *
********************************

php.ini used by PHP: /etc/php5/cli/php.ini

** WARNING **
*  The PHP CLI can use a different php.ini file
*  than the one used with your web server.
*  If this is the case, please launch this
*  utility from your web server.
** WARNING **

** Mandatory requirements **

  OK        PHP version is at least 5.2.4
  OK        php.ini has zend.ze1_compatibility_mode set to off

** Optional checks **

  OK        PDO is installed
  OK        PDO has some drivers installed: mysql
  OK        PHP-XML module is installed
  OK        XSL module is installed
  OK        The token_get_all() function is available
  OK        The mb_strlen() function is available
  OK        The iconv() function is available
  OK        The utf8_decode() is available
  OK        A PHP accelerator is installed
  OK        php.ini has short_open_tag set to off
  OK        php.ini has magic_quotes_gpc set to off
  OK        php.ini has register_globals set to off
  OK        php.ini has session.auto_start set to off
  OK        PHP version is not 5.2.9


Si seulement quelques 'warning' apparaissent, ce n'est pas trop grave.
Faite les correctifs demander dans le fichier php.ini utilisé (Attention il peut y en avoir plusieurs)

Sur linux il est assez facile d'ajouter les modules manquants, c'est plus compliqué sur MAC et Windows et pas forcement nécessaire à ce stade.

Par contre si le résultat vous signale une mauvaise configuration, il faudra faire le nécessaire pour corriger le problème avant d'aller plus loin.

 - Créer la structure du projet symfony

## Pour MAC / LINUX
php lib/vendor/symfony/data/bin/symfony generate:project egallery

## Pour windows:
php lib\vendor\symfony\data\bin\symfony generate:project egallery

## On peut lister les différentes commandes de symfony en tapant:
php symfony

En ajoutant l'option -V à la fin, on peut voir le version du framework.

2) Créer l'application.

php symfony generate:app --escaping-strategy=on --csrf-secret=Unique$ecret frontend

Les 2 options --escaping-strategy et --csrf-secret sont pour la sécurité.
Pour plus de détails allez voir le projet symfony.

Cette méthode créer l'arborescence nécessaire au projet dans le répertoire apps/frontend/

3) Configurer l'application.

  - Symfony utilise une classe php pour faire les imports automatique des classes dont il a besoin.
Pour une meilleur portabilité de code il faut changer le chemin de cette classe dans le fichier config/ProjectConfiguration.class.php :

   require_once dirname(__FILE__) . '/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';

 
 - Pour une meilleure gestion des projets je conseille de forcer le fichiers hosts:
  Pour Linux et MAC, il est dans le répertoire /etc/hosts
  Pour windows, il est dans le répertoire C:\WINDOWS\system32\drivers\etc\

Un fois le mode lecture seul enlevé sur Windows, ajouter cette ligne:

127.0.0.1    egallery.localhost

Note: Cette manipulation permet de créer un nom de domaine virtuellement (uniquement sur la machine).
En générale c'est comme ceci que les Admin réseau testent la migration de sites avant de basculer les DNS.

 4) Configurer le serveur:

 - Vérifier la présence et Activer si nécessaire des module mod_vhost_alias et mod_rewrite.
 - Créer le Virtual host dans Apache2:

Linux : Créer un fichier /etc/apache2/sites-enabled/egallery.conf
Mac/Win : Editer le fichier httpd.conf ou apache2.conf
On peut également créer un VirtualHost dans un fichier à part de la conf d'Apache mais je ne m'étendrais pas dessus ici.

Il faut dire à apache que la racine de l'application est dans le répertoire web/ de symfony.
Remplacer les chemins /projects/egallery/trunk/deploy/ par c:\projects\egallery\trunk\deploy\ pour windows.
<VirtualHost *:80>
  ServerName egallery.localhost
  DocumentRoot "/projects/egallery/trunk/deploy/web"
  DirectoryIndex index.php
  <Directory "/projects/egallery/trunk/deploy/web">
    AllowOverride All
    Allow from All
  </Directory>
  Alias /sf /projects/egallery/trunk/deploy/lib/vendor/symfony/data/web/sf
  <Directory "/projects/egallery/trunk/deploy/lib/vendor/symfony/data/web/sf">
    AllowOverride All
    Allow from All
  </Directory>
</VirtualHost>

- Relancer Apache et tester l'URL http://
egallery.localhost
Si tout est bien configurer vous devez avoir la page d'accueil de symfony comme ceci:

II/ Développer l'application frontend


  1) Installer Doctrine 1.1

Note: Pendant tout cette partie, l'application sera inutilisable. Elle sera à nouveau opérationnelle à la fin de la configuration de la BDD (partie 2: créer la BDD).

Symfony à la possibilité d'utiliser 2 ORM pour la gestion des Models de BDD : Propel ou Doctrine.
En l'occurence depuis la version 1.2 de Symfony, de plus de en plus de developpements avec Symfony se font avec cet ORM. Fabien Potencier, le créateur de symfony, recommande également d'utiliser Doctrine.
C'est pourquoi nous allons installer le plugin de doctrine.

 - Première étape il faut désactiver complètement propel.
 - Editer la classe ProjectConfiguration dans le repertoire config
 - Remplacer cette ligne :

$this->enableAllPluginsExcept(array('sfDoctrinePlugin', 'sfCompat10Plugin'));

 - Par celle-ci:

$this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin'));

 - Supprimer les fichiers:

config/databases.yml
conf/propel.ini
conf/schema.yml
web/sfPropelPlugin

  - Publier à nouveau les plugins:

php symfony plugin:publish-assets

 - Résultat de la commande:

>> plugin    Configuring plugin - sfDoctrinePlugin
>> plugin    Configuring plugin - sfProtoculousPlugin

  - Vider le cache de symfony:

php symfony cc

Note: cette commande est très importante dans projet symfony, je vous conseille vivement de lire les tutorials à ce sujet ici

  2) Créer la BDD

 - Créer la BDD.

mysqladmin -uroot -p create egallery
>> Enter password: ******

Note: Changer le chemin de la commande mysqladmin avec le bon chemin selon la configuration de votre machine ou créer une une BDD en UTF8 dans phpmyadmin.

 - Configurer l'accès à la BDD dans symfony:

php symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=jobeet" root myPassSecret

- Le fichier conf/database.yml est créé:

all:
  doctrine:
    class: sfDoctrineDatabase
    param:
      dsn: 'mysql:host=localhost;dbname=egallery'
      username: root
      password: myPassSecret

Note: Eviter de configurer un accès à la BDD avec un utilisateur root, il vaux mieux créer un utilisateur spécifique à cette BDD.

 - Créer le schéma de la BDD:

nano config/doctrine/schema.yml

# config/doctrine/schema.yml
Gallery:
  columns:
    id: { type: integer(4), primary: true, autoincrement: true }
    description: { type: string, notnull: true }
    title: { type: string(150), notnull: true }
    url: { type: string(255), notnull: true }

# create the application model
php symfony doctrine:build-model
>> doctrine  generating model classes

# create the sql structure file
php symfony doctrine:build-sql
>> doctrine  generating sql for models

# launch the sql structure file
php symfony doctrine:insert-sql
>> doctrine  created tables successfully

# build the schema
php symfony doctrine:build-schema
>> doctrine  generating yaml schema from database
>> doctrine  Generate YAML schema successfully from database

Note: Pour plus de détail sur la création et la configuration d'une BDD avec Doctrine consulter la documentation en ligne: http://www.doctrine-project.org/
Pour la configuration d'un fichier yml avec doctrine: http://www.doctrine-project.org/documentation/manual/1_1/en/yaml-schema-files.
Pour en savoir plus sur le format YAML, allez consulter cette adresse: http://yaml.org/
Plusieurs plugins eclipse existent pour prendre en charge ce format de fichier, personnellement j'utilise celui-là : YEdit

 - Créer le fichier data/fixtures/gallery.yml:

# data/fixtures/gallery.yml
gallery:
<?php for( $i = 1 ; $i <= 7 ; $i++ ): ?>
  picture_<?php echo $i ; ?>:
    description : The Picture n°0<?php echo $i ; ?>
    title : Picture 0<?php echo $i ; ?>
    url : picture/picture<?php echo $i ; ?>.jpg
<?php endfor ; ?>

Ce fichier servira à remplir la BDD.
Note : Il est possible dans un fichier .yml de

- Insérer les données dans la Base:

php symfony doctrine:data-load
>> doctrine  loading data fixtures from "/ho...ery/trunk/deploy/data/fixtures"
>> doctrine  Data was successfully loaded

Note: Il faut faire attention aux espaces dans le fichiers yml sinon la commande peut générer une erreur du genre:    


  Validation failed in class Gallery

    2 fields had validation errors:

      * 1 validator failed on title (notnull)     
      * 1 validator failed on url (notnull)       

Voire ce message:


Unable to parse string: Unable to parse line 4 (  picture_2:).    

Le problème vient du mauvais parsing du fichier yml qui ne trouve pas les champs title et url.
On peut bien s'en rendre compte en changeant le fichier schema.yml et en relançant tout le process:

# config/doctrine/schema.yml
Gallery:
  tableName: gallery
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    description:
      type: string(2147483647)
      notnull: true
    title:
      type: string(150)
      notnull: false
    url:
      type: string(255)
      notnull: false

# rebuild all the database schema
php symfony doctrine:build-all-reload
                                                       
  This command will remove all data in your database.  
  Are you sure you want to proceed? (y/N)              
                                                       

y
>> doctrine  dropping databases
>> doctrine  Successfully dropped database f...ion "doctrine" named "egallery"
>> doctrine  creating databases
>> doctrine  Successfully created database f...ion "doctrine" named "egallery"
>> doctrine  generating model classes
>> doctrine  generating sql for models
>> doctrine  Generated SQL successfully for models
>> doctrine  generating form classes
>> doctrine    /home.../base/BaseGalleryForm.class.php
>> tokens    /home.../doctrine/GalleryForm.class.php
>> tokens    /home...rine/BaseFormDoctrine.class.php
>> doctrine  generating filter form classes
>> tokens    /home...BaseGalleryFormFilter.class.php
>> tokens    /home...ine/GalleryFormFilter.class.php
>> tokens    /home...aseFormFilterDoctrine.class.php
>> doctrine  created tables successfully
>> doctrine  loading data fixtures from "/ho...ery/trunk/deploy/data/fixtures"
>> doctrine  Data was successfully loaded

En regardant les données insérées dans le BDD, on peut voir que les champs title et url ont sont vide et que les données ont été concaténées dans le champs description.

  3) Installer le plugin sfAmfPlugin

Pour comprendre ce qu'est un plugin symfony, je voie recommande de faire le tutorial jobeet en langue anglaise (il est plus complet).
On apprend cette notion lors du 20ème tuto: http://www.symfony-project.org/jobeet/1_2/Doctrine/en/20
Le plugin qui nous intéresse se trouve: http://www.symfony-project.org/plugins/sfAmfPlugin
Basé sur SabreAMF il est plus utilisé et plus stable que celui reprenant AmfPHP.
De plus, le projet AmfPHP n'est plus du tout maintenu et la question est: Est-ce qu'il est optimal d'utiliser cette encore techno dans les projets flash ? 

Nicolas Perriault, quant à lui, a publié un billet sur son blog expliquant l'installation de AmfPHP dans un projet symfony:
http://prendreuncafe.com/blog/post/2008/07/25/Partager-la-session-utilisateur-entre-Flash/Flex-et-symfony-avec-AmfPHP

L'interêt du plugin sfAmfPlugin c'est l'interaction avec l'ORM Doctrine pour faciliter la création des requête SQL.
On l'installe comme ça:

# install processing
$ php symfony plugin:install sfAmfPlugin

>> plugin    installing plugin "sfAmfPlugin"
>> sfPearFrontendPlugin Attempting to discover channel "pear.symfony-project.com"...
>> sfPearFrontendPlugin downloading channel.xml ...
>> sfPearFrontendPlugin Starting to download channel.xml (663 bytes)
>> sfPearFrontendPlugin .
>> sfPearFrontendPlugin ...done: 663 bytes
>> sfPearFrontendPlugin Auto-discovered channel "pear.symfony-project.com", alias
>> sfPearFrontendPlugin "symfony", adding to registry
>> sfPearFrontendPlugin Attempting to discover channel
>> sfPearFrontendPlugin "plugins.symfony-project.org"...
>> sfPearFrontendPlugin downloading channel.xml ...
>> sfPearFrontendPlugin Starting to download channel.xml (639 bytes)
>> sfPearFrontendPlugin ...done: 639 bytes
>> sfPearFrontendPlugin Auto-discovered channel "plugins.symfony-project.org", alias
>> sfPearFrontendPlugin "symfony-plugins", adding to registry
>> sfPearFrontendPlugin downloading sfAmfPlugin-1.4.2.tgz ...
>> sfPearFrontendPlugin Starting to download sfAmfPlugin-1.4.2.tgz (49,336 bytes)
>> sfPearFrontendPlugin ...done: 49,336 bytes
>> sfSymfonyPluginManager Installation successful for plugin "sfAmfPlugin"

# clear the cache
php symfony cc

  Nota: la version svn et le package téléchargé via PEAR n'est à priori pas la même.

  4) Créer le gateway

# the command to create the gateway module
php symfony generate:module frontend amfgateway
>> dir+      /home...tend/modules/amfgateway/actions
>> file+     /home...teway/actions/actions.class.php
>> dir+      /home...nd/modules/amfgateway/templates
>> file+     /home...eway/templates/indexSuccess.php
>> file+     /home...ntend/amfgatewayActionsTest.php
>> tokens    /home...ntend/amfgatewayActionsTest.php
>> tokens    /home...teway/actions/actions.class.php
>> tokens    /home...eway/templates/indexSuccess.php

Ensuite il faut créer l'instance du gateway dans le module.

<?php

/**
 * amfgateway actions.
 *
 * @package    egallery
 * @subpackage amfgateway
 * @author     NairuS
 * @version    SVN: $Id: actions.class.php 12479 2008-10-31 10:54:40Z fabien $
 */
class amfgatewayActions extends sfActions
{
    /**
     * Executes index action
     *
     * @param sfRequest $request A request object
     */
    public function executeIndex( sfWebRequest $request )
    {
        $this->setLayout( false ) ;
       
        $gateway  = new sfAmfGateway() ;
        $response = sfContext::GetInstance()->getResponse() ;
        $response->setContent($gateway->service()) ;
        return sfView::NONE ;
    }
}
?>

Note : La méthode setLayout et la propriété sfView::NONE permettent à symfony de ne pas afficher de view lors de l'appel à cette action.
On peut donc sans crainte supprimer la view indexSuccess dans le répertoire template que la gérénation du module à créer par défaut.

5) Créer le service getGallery

# the command to create the service
php symfony amf:create-service --package=egallery/net/remoting Gallery
>> dir+      /home...services/egallery/net/remoting/
>> file+     /home...moting/GalleryService.class.php
>> tokens    /home...moting/GalleryService.class.php

L'option --package= est optionnel et permet de créer des répertoires pour classer les services.

6) Renvoyer le model de picture à afficher dans le flash.

<?php
/**
 * AMF enabled service class GalleryService
 *
 * Project: egallery
 *
 * @package   egallery.net.remoting
 * @author    NairuS
 *
 * @version SVN: $Id $
 */
class GalleryService extends sfAmfService
{
    /**
     * Returns all the Pictures of the DB
     * @return Array of PictureVO
     */
    public function getPictures()
    {
        $datas = Doctrine::getTable( 'Gallery' )->findAll() ;
        return $datas->getData();
    }
}
?>

        La commande Doctrine::getTable( 'Gallery' )->findAll() renvoie un objet Doctrine_Collection.
        La méthode getData() renvoie le tableau d'objets Doctrine_Record de la table en question.

          Note : Après toutes commandes symfony pensez a vider le cache pour forcer aussi d'Autoload des nouvelles classes ajoutées:

php symfony cc


III/ Intégrer le flash.


  1) Exporter le projet eGallery.

    - Exporter les sources du projet dans le répertoire trunk.

svn export http://astr.googlecode.com/svn/trunk/AS3/trunk/examples/egallery/advanced/trunk/ path/to/dir

    Attention : Etant donné que le répertoire trunk existe déjà, la commande ci-dessus ne fonctionnera pas. Dans ce cas, il faut l'exporter hors du projet et déplacer les sources dans le répertoire trunk.
    Le répertoire deploy doit également faire preuve d'une grande attention. Plus haut dans le tutoriel nous y avons installé le framework symfony.
    Grâce à son moteur de rooting nous faisons pointer le root du VirtualHost dans le répertoire web.
    C'est donc ici qu'il faudra y mettre les fichiers du répertoire deploy de egallery. Nous n'avons pas besoin du fichier index.html ici mais il nous servira à construire la page d'accueil du site plus tard.
   
    Nous n'avons pas besoin du tout du répertoire php qui contient amfphp puisque c'est symfony qui prendra le relais. Nous pouvons le supprimer.
    Les fichiers css doivent être déplacer du répertoire style vers le répertoire css pour que symfony puisse les prendre en compte plus tard.
    Un fois cette manipulation faite on peut supprimer le répertoire style.

    Avant de 'commiter' ces modifications vérifier qu'il n'y a pas de fichiers que vous ne voulais pas envoyer.

egallery/trunk$ svn st
?      test
?      .as3_classpath
?      src
?      bin
?      libs
?      .settings
?      deploy/test/functional/frontend/homeActionsTest.php
?      deploy/apps/frontend/modules/home
M      deploy/apps/frontend/templates/layout.php
?      deploy/web/context
?      deploy/web/locale
?      deploy/web/library
?      deploy/web/index.swf
?      deploy/web/config
?      deploy/web/css/egallery.css
?      deploy/web/css/index.css
?      deploy/web/js/swfobject.js

    Ici on peut se rendre compte qu'il y a des fichiers de eclipse et FDT qui se sont mis dans les fichiers non versionné du svn.
    Il faut les ignorer avec la commande:

svn propedit svn:ignore .

    Après l'argument svn:ignore il faut définir le chemin ou on veut ajouter des fichiers à ignorer. Dans notre cas on met un . car on se situe déjà dans le répertoire souhaité.
    Votre éditeur par défaut s'ouvre et il suffit ajouter ces lignes:

*.project
*.as3_classpath
*.settings

    Un fois que vous avez sauvegarder vous devez avoir ce résultat:

egallery/trunk$ svn propedit svn:ignore .
Nouvelle valeur définie pour la propriété 'svn:ignore' sur '.'
egallery/trunk$ svn st
M      .
?      test
?      src
?      bin
?      libs
?      deploy/test/functional/frontend/homeActionsTest.php
?      deploy/apps/frontend/modules/home
M      deploy/apps/frontend/templates/layout.php
?      deploy/web/context
?      deploy/web/locale
?      deploy/web/library
?      deploy/web/index.swf
?      deploy/web/config
?      deploy/web/css/egallery.css
?      deploy/web/css/index.css
?      deploy/web/js/swfobject.js

    C'est bon, vous pouvez envoyer les modifications sur votre dépôt.

    2) Modifier les sources pour adapter le remoting à celui de symfony.  

    2.1. Adapter l'IoC:

    Dans un premier temps, il faut changer l'appel au gateway et au service remoting.
        - Editer le fichier web/context/net/remoting/getGallery.eden et changer ces 3 lignes:

{ name : "methodName"     , value : "getPictures"               } ,
{ name : "objectEncoding" , value : 3                           } ,
{ name : "serviceName"    , value : "egallery.net.remoting.GalleryService" } ,

    Ces modifications permettent de définir le bon service remoting à appeler par le protocole AMF3.
    
        - Changer le gateway en éditant le fichier config/config.eden:

gatewayUrl   = "http://{0}/frontend_dev.php/amfgateway" ;
httphost     = "egallery.localhost" ;

    Il faut cibler l'environnement dev de symfony sinon on aura un timeout sur le remoting à tout les coups.
    Cette notation {0} permet de remplacer cette valeur par la variable httphost définit dessous grâce à la classe system.Strings de maashaack.
    
    La variable httphost sera écraser en prod par le httphost du serveur de WEB qui sera passer en flashvars par le PHP.
   
    Note: Avec le plugin que j'ai installé, je dois cibler l'environnement 'frontend_dev.php' pour que le protocole AMF puisse se faire.
Mon collègue n'a pas réussi avec cette configuration et a dû cibler l'environnement de prod pour ça.

    2.2. Adapter le remplissage du model AS3:

Le plugin sfAmfPlugin basé sur SabreAMF, permet de faire du class mapping en annotant le tag @AmfClassMapping dans le PHPDoc du service.
Dans notre cas on ne renvoie pas un objet à mapper mais une collection d'objets. Ce qui, à ma connaissance d'aujourd'hui ne fonctionne pas.
Après plusieurs tests, ce tag renvoie bien un PictureVO, seulement ce ne sont pas les enregistrements qui sont 'mappés' mais la collection.

Pour utiliser le class mapping de SabreAMF, il faudrait renvoyer de simple objet.
Par exemple on souhaitait retourner un seul PictureVO via remoting on ajouterait cette methode au service:

/**
 * Returns a Picture of the DB
 *
 * @param Integer $id
 * @return PictureVO
 * @AmfClassMapping(name="egallery.vo.PictureVO")
 */
public function getPicture( $id )
{
    return Doctrine::getTable( 'Gallery' )->find( $id )
}


Mais grâce à la puissance de l'AS3, avec son type * et l'implémentation du for ... in , nous avons la possibilité de contourner le problème.

En effet, la class vegas.vo.SimpleValueObject permet de récupérer un objet générique et de le parcourir propriété par propriété pour récupérer les valeurs qui correspondent à notre objet flash.
        Commençons par éditer le fichier egallery.net.remoting.GetGallery

# dans la méthode result remplacer la première ligne par la seconde.
model.addVO( result[i] as PictureVO ) ;
model.addVO( new PictureVO( result[i] ) ) ;

        Il faut ensuite recompiler l'application avec FDT ou autre logiciels pour les application AS3.
       

    3) Finaliser le site.

    3.1. Créer le module home pour l'accueil.

# The argument init-module is a shortcut to the generate:module command
php symfony init-module frontend home

>> dir+      /home/sfpro...s/frontend/modules/home/actions
>> file+     /home/sfpro.../home/actions/actions.class.php
>> dir+      /home/sfpro...frontend/modules/home/templates
>> file+     /home/sfpro...home/templates/indexSuccess.php
>> file+     /home/sfpro...al/frontend/homeActionsTest.php
>> tokens    /home/sfpro...al/frontend/homeActionsTest.php
>> tokens    /home/sfpro...home/actions/actions.class.php
>> tokens    /home/sfpro...home/templates/indexSuccess.php

    3.2. Intégrer les CSS et JS.

    - Editer le fichier config/view.yml:

# ajouter les css et js dans les propriétés suivantes:

stylesheets:    [main.css, index.css, egallery.css]
 
javascripts:    [swfobject.js]

    3.3. Modifier les templates

    - Supprimer le doctype dans le layout principal dans le fichier apps/frontend/templates/layout.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

        Cette balise, pour raison que j'ignore encore, empêche l'affichage du swf à 100%.
        Je l'ai donc supprimé pour afficher le swf en 100%.

        Note: on a la possibilité avec symfony de créer d'autre layout et de les charger dans n'importe quelles views avec les fichiers YML.

   - Ecrire dans le template indexSuccess.php:

<script language="JavaScript" type="text/javascript">
    var flashvars  =
    {
       httphost: "<?php echo $_SERVER['HTTP_HOST'] ; ?>"
    } ;
    
    var params =
    {
        allowScriptAccess : "sameDomain"  ,
        bgcolor           : "#666666"  ,
        quality           : "high"
    } ;
    
    var attributes =
    {
        align : "middle"
    } ;
    swfobject.embedSWF("/index.swf", "application", "100%", "100%", "9.0.124", false, flashvars, params, attributes );
</script>
<div id="application">
    <a href="http://www.adobe.com/go/getflashplayer">
        <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
    </a>
</div>


   3.3. Créer le rooting vers la page d'accueil.

    - Editer le fichier apps/frontend/config/routing.yml et  remplacer par ce code:

# default rules
homepage:
  url:   /
  param: { module: home, action: index }

default_index:
  url:   /:module
  param: { action: index }

default:
  url:   /:module/:action/*


    - Faire un clear-cache pour que symfony puisse prendre en compte le nouveau routing.
Ce tuto est également disponible en google doc public:  http://docs.google.com/View?id=ddqrq3hp_59d66w6zgk
Dans les sources du projet je n'ai pas mis le framework Vegas mais les sources sont disponible ici.

dimanche, juin 28 2009

Test de bench avec l'instruction 'with' en AS3

Hello,

Il y a quelques jours j'ai appris une autre méthode de développement utilisée massivement dans le framework Symfony: le Fluent Interface

Je trouvais ça pas mal à tester en action Script 3 mais au final c'est une méthode lourde qui oblige à créer des méthodes setCustom pour renvoyer l'instance de l'objet.

En AS3 les propriétés virtuelles get/set sont bien plus performantes à mon sens.

Du coup après en avoir discutér avec eKa, j'ai voulu tester la rapidité de l'instruction 'with' en AS3.

Le résulat est édifiant. L'instruction "with" est quasiment 3 fois plus lente que la méthode classique.

Voici le code qui en atteste:

var o:Object = { x : 0 , y : 0 , h : 100 , w : 100 } ;

trace( ">>>> first test" ) ;

var i:uint ;
var startT:Number = getTimer();

i=0 ;
for( i ; i < 50000 ; i++ )
{
    o.x += i ;
    o.y += i ;
    o.h *= i ;
    o.w *= i ;
}
trace( trace( getTimer() - startT ) ) ; // 50

trace( ">>>> second test" ) ;
startT = getTimer();
i=0 ;
for( i ; i < 50000 ; i++ )
{
    with(o)
    {
        x += i ;
        y += i ;
        h *= i ;
        w *= i ;
    }
}
trace( trace( getTimer() - startT ) ) ; // 139

Pour aller plus loin, on pourrait tester ce code avec des propriétés virtuelles:

// la classe Test

package
{
    public class Test
    {
         public function Test()
         {
             //
         }
    
         private var _x:uint ;
         private var _y:uint ;
         private var _h:uint ;
         private var _w:uint ;
         
         public function get x():uint
        {
             return _x ;
         }
         public function set x( value:uint ):void
        {
             _x = value ;
         }
         
         public function get y():uint
         {
             return _y ;
         }
         public function set y( value:uint ):void
         {
             _y = value ;
         }
         
         public function get h():uint
         {
             return _h ;
         }
         public function set h( value:uint ):void
         {
             _h = value ;
         }
         
         public function get w():uint
         {
             return _w ;
         }
         public function set w( value:uint ):void
         {
             _w = value ;
         }
    }
}

// le bench

var o:Test = new Test() ;

trace( ">>>> first test" ) ;

var i:uint ;
var startT:Number = getTimer();
i=0 ;
for( i ; i < 50000 ; i++ )
{
    o.x += i ;
    o.y += i ;
    o.h *= i ;
    o.w *= i ;
}

trace( trace( getTimer() - startT ) ) ; // 32

trace( ">>>> second test" ) ;

startT = getTimer();
i=0 ;
for( i ; i < 50000 ; i++ )
{
    with(o)
   {
        x += i ;
        y += i ;
        h *= i ;
        w *= i ;
    }
}
trace( trace( getTimer() - startT ) ) ; // 132

Les propriétés virtuelles sont encore plus rapides!

Au final, je pense que l'AS3 est vraiment orienté et optimisé pour la POO et qu'il faut le pratiquer comme tel.

Vivement l'AS4 (s'il arrive un jour!)

A++ NairuS

vendredi, mars 27 2009

Supprimer les tags bom ' ' des fichiers UTF-8

Hello,

Hier, j'ai eu un problème avec un script d'upload php sur un de mes serveurs en ligne. Mon script génére grace à fpdf un pdf dynamique et l'upload à la volée.

Lors du test en local, j'ai eu un problème du genre : Warning: Cannot modify header information - headers already sent by (output started at script.php:X)

Sur le site de FPDF, ils indiquent la marche à suivre dans la question 9 de la FAQ. Il faut rajouter la function ob_end_clean(); pour vider les headers envoyés au serveur.

Ce que je fis. Mon problème fut résolu en local. Une fois passé en ligne, j'ai eu à nouveau le même message d'erreur à la différence qu'au début de mon script, avant ma balise PHP j'avais ces caractères  qui faisaient que le script ne pouvait pas lancer le téléchargement.

En cherchant à résoudre ce problème avec mon ami Google, j'ai trouvé la solution.

En fait certains de mes fichiers étaient encodés en UTF-8 avec BOM avec Eclipse et c'est ce qui créa ce problème en ligne. Pour supprimer ces caractères disgracieux, j'ai trouvé un logiciel gratuit et open-source qui permet de supprimer ces caractères de tous les fichiers dans un répertoire.

http://hidalgoemmanuel.info/csharp-net/supprimer-les-tag-bom-dans-fichier-utf-8.html

Mon problème a été résolu en deux temps, trois mouvements!! Cool !!

A++ NairuS ;)

dimanche, mars 1 2009

AMPPHP intégré dans WordPress

Hello,

Un post rapide pour diffuser une info à mon sens importante pour les développeurs Flash/PHP comme moi!!

Un nouveau projet open source sur google code vient d'être publié.

Il se nomme AWI (AMFPHP WordPress Integration).

J'ai eu l'info dans mes RSS et il me semble pas mal de le diffuser sur mon blog.

Ce serait un plug-in AMPPHP dans WordPress pour gérer les nuages de Tags.

Je n'ai pas encore approfondi le projet mais il me semble intéressant.

A++ NairuS ;)

lundi, février 23 2009

Ma première réalisation en ligne

Bonjour,

Un petit post rapide pour dire que le premier site réalisé entièrement par moi est en ligne depuis une semaine.

C'est le site galerie d'un couple d'amis qui font du théâtre, de la vidéo, de l'illustration et de la photo.

J'utilise des techno comme CakePHP pour le coeur du site et Ast'r pour la galerie photos.

www.unoeildanslemonde.fr

NairuS ;)

mardi, février 3 2009

Attention aux SMS frauduleux

Hello,

Depuis plusieurs mois des SMS avec un numéro de type 089... circulent sur nos mobiles en nous disant ce genre de messages : « salut, c'est moi....rappelle-moi au 089...... » « ca m'amuse pas de t'envoyer des SMS... ».

Attention si vous répondez vous allez faire le régal de votre opérateur de téléphone et le malheur de votre portefeuille ;)

Je fais ce post rapide pour mettre en garde encore ceux qui n'auraient pas été victimes de ces SMS frauduleux.

Allez voir cet article : http://www.zdnet.fr/actualites/telecoms/0,39040748,39386922,00.htm?xtor=RSS-1

A++ NairuS ;)

samedi, janvier 31 2009

Styliser un LabelButton de LunAS en IoC

Hello,

Ce tuto s'adresse plus particulièrement à des utilisateurs avancés de l'IoC de VegAS.

Il faut avoir des base de design pattern et avoir lu la documentation complète d'eKameleon  à ce sujet ici.

Je souhaitais expliquer comment changer le style des buttons de LunAS en général et en particulier un LabelButton.

Pour ceux qui utilise l'extension LunAS de VegAS doivent savoir que les composants sont construits grâce à 2 classes : le builder et de style.

C'est la classe qui gère le style qui va nous intéresser ici en l'occurence LabelButtonStyle.

Pour styler pleinement ce genre de bouton il faut combiner deux stratégies :

  • La feuille de style CSS.
  • Les propriétés à modifer dans la config.

Pour modifier la feuille CSS il suffit simplement de rajouter une classe du même nom que la valeur de la propriété labelStyleName et de changer les propriétés que l'on veut :

.label_button_label{
   
 font-familyVerdana;
    
font-size12px;
    
font-weightbold;
    font-style: italic;
}

A ce niveau on peut se rendre compte qu'il manque la stylisation du rollover sur le bouton par exemple. 
C'est là que rentre en jeu la classe LabelButtonStyle et ses propriétés.

Grâce à l'IoC de VegAS, il est possible de modifier au runtime ses propriétés.

Pour ce faire il suffit de définir dans un fichier eden, par exemple style.eden la classe LabelButtonStyle et de modifier ses propriétés directement dans la définition ou en se servant d'un fichier de config externe (CF. le tutoriel sur le Designe Pattern d'Ekameleon partie C).

Nota: Ne pas oublier de mettre cette classe dans le tableau LinkageInforcer  de l'application si aucune classe dépendante de celle-ci y sont déclarée.

Dans tous les cas si l'IoC fait une erreur au runtime c'est qu'il manque certainement cette classe dans le swf.

Nota2: Pour changer la couleur du texte selon son état il faut veiller à ce que la propriété useTextColor soit bien définit à true (sa valeur par défaut).

  • Le fichier style.eden:

objects =
[
    {
        id         :
"label_button_style" ,
        type       :
"lunas.display.button.LabelButtonStyle" ,
        singleton  :
true ,
        properties :
        [
            { name:"color"             , value:"#801127"     },
            { name:"textDisabledColor" , value:"0xCCCCCC"    },
            { name:"textRollOverColor" , value:"0xE31B3C"    },
            { name:"textSelectedColor" , value:"0x999999"    },
            { name:"styleSheet"        , ref  :"style_sheet" },
            { name:"useTextColor"      , value:true          } 
        ]
     }
] ;

  • Pour terminé, il ne faut pas oublié de cabler cette classe dans le LabelButton :

...
{
    id         : "my_button" ,
    type       : "lunas.display.button.LabelButton" ,
    singleton  : true ,
    lazyInit   : true ,
    properties :
    [
        { name : "label" , locale : "my_button.label"    } ,
        { name : "style" , ref    : "label_button_style" } ,
        { name : "w"     , config : "my_button.w"        } ,
        { name : "x"     , config : "my_button.x"        } ,
        { name : "y"     , config : "my_button.y"        }
    ]
}
...

Voici un exemple en pure flash:
import lunas.display.button.LabelButton ;

import lunas.display.button.LabelButtonStyle ;


import system.serializers.eden.EdenSerializer ;


// custom style
var style:LabelButtonStyle = new LabelButtonStyle() ;

style.color                = 0x801127 ;
style.textDisabledColor    = 0xCCCCCC ;
style.textRollOverColor    = 0xE31B3C ;
style.textSelectedColor    = 0x999999 ;

style.styleSheet.setStyle( "." + style.labelStyleName , { fontFamily:"verdana", fontSize:"14px", fontWeight:"normal" , fontStyle:"italic" } ) ;

// default label button.

var bt1:LabelButton = new LabelButton() ;
bt1.x     = 10 ;
bt1.y     = 10 ;
bt1.label = "Default LabelButton" ;


// cutom label button

var bt2:LabelButton = new LabelButton() ;
bt2.width = 200 ;
bt2.x     = 10 ;
bt2.y     = 30 ;
bt2.label = "Custom LabelButton" ;

bt2.style = style ;

addChild( bt1 ) ;
addChild( bt2 ) ;

Pour conclure, cette technique permet de rendre extrêmement souple l'utilisation des composants de LunAS.
On peut imaginer définir autant de style que l'on veut dans une application et les appliqués sur les boutons que l'on souhaite.

C'est pour ça que si vous êtes intéressé par la puissance du framework d'eKameleon , je vous invite à regarder  et utiliser son framework d'application ASTr'.

vendredi, janvier 30 2009

Homer comme page d'Erreur 404 trop top!

Hello,

J'adore cette page d'erreur 404 façon Homer Simpon.

http://www.artthugstudios.com/error404.html

;)

PS : trouvé dans cette news http://www.smashingmagazine.com/2009/01/29/404-error-pages-one-more-time/

lundi, janvier 26 2009

De l'art 3D proche de la perfection!!

Hello,

Je poste ce rapide billet pour tirer mon chapeau à cet artiste qui fait des créations en 3D et autres de grandes qualités.

Les créations en 3D qu'il crée approchent de la perfection.

Allez voir sa galerie de photos.

http://www.androidblues.com/

Du grand Art!!

A++

NairuS ;)

lundi, janvier 12 2009

Problème avec le passage du caractère € en AMFPHP et Flash

Bonjour,

Il y a quelques jours j'ai eu un problème de passage du caractère € de ma base de données à Flash via AMFPHP.

La table Unicode de flash renvoie un EntityNumber de 8364 alors que celle de PHP 128.

Du coup quand on passe un caractère € de AMFPHP à Flash, il ne s'affiche pas correctement.

Examples :

  • PHP
<?php
ord( '€' ) ; // 128
echo chr( 128 )  ; // €
?>

NOTE: Le document a été enregistré au format ANSI sous notepad++. Lorsque que je l'enregistre en UTF-8, la fonction me renvoie '226'.

  • Flash
  • var str:String ;
    str = "€" ;
    trace( str.charCodeAt( 0 ) ) ; // 8364
    trace( str.fromCharCode( 8364 ) ) ; // €

    Du coup pour pallier à ce problème de table Unicode j'ai fait un portage PHP de la class asgard.net.HTMLEntities.as ( doc : http://vegas.ekameleon.net/docs )

    J'ai ajouter cette fonction statique pour pallier le problème :

    public static function convertEuroCharForFlash( $str )
    {
    return preg_replace( "/\x80/" , "&#x20AC;" , $str ) ;
    }
    

    La regex remplace tous les caractères dont le nombre hexadécimal est égal à 80 en nombre hexadecimal unicode du caractère .

    Pour faire mes tests et trouver les valeurs UNICODE j'ai trouvé ce convertisseur sur internet : http://hapax.qc.ca/conversion.fr.html

    Mais on peut aussi faire ça:

    <?php echo dechex( ord( '€' ) ) ; // 80 ?>

    dimanche, janvier 11 2009

    Un Malware faussement appellé Ikea circule sur par email

    Bonjour,

    En parcourant le web je suis tombé sur cette article : http://www.commentcamarche.net/actualites/un-malware-faussement-signe-ikea-circule-par-email-5848107-actualite.php3

    Il signale que le groupe Ikea informe ces clients qu'un cheval de troie circule sur son nom et recommande de supprimer ce mail.

    Donc prudence !!

    NairuS

    mercredi, janvier 7 2009

    Il neige sur Marseille !!

    Hello,

    Ce poste n'a rien à voir avec le but de mon blog mais c'est juste un commentaire sur l'hallucinante tombée de neige sur Marseille ce matin.

    Depuis 7 heures ce matin il neige à Marseille comme à Dunkerque!! Je ne suis jamais aller à Dunkerque mais j'imagine!! ;)

    Habituellement je prends le vélo pour me rendre à mon travail (Ce que j'ai fait ces deux derniers jours). Mais aujourd'hui c'est vraiment pas possible!!

    J'ai 10 km à faire, et j'ai mis plus de temps en voiture ce matin qu'en vélo hier.

    Ca patine à toutes les reprises de feux!

    C'est rare de voir Marseille ensevellie par la neige comme ça! C'est magnifique!!!!!

    La corniche toute blanche, le david tout nu dans la neige. Il doit avoir froid! ;)

    La circulation c'est aussi quelque chose. Les voitures roulent à 20km/h, pas de klaxons, pas d'injures.

    Les gens attendent que la voiture de devant passe la passerelle de la capelette avant de s'y engager.

    Bref on se croirait dans le film de Keanu Reeves, Le jour d'après.

    Je suis sûr que la cause de tout ça, c'est le réchauffement climatique!!!....

    Ou pas !

    Je posterai des photos plus tard pour voir prouver mes dires!!

    A++ NairuS

    dimanche, janvier 4 2009

    Bonne Année 2009 !

    En ce début d'année 2009, j'ai décidé de lancer mon blog sur internet afin de faire connaitre ma passion et mon activité professionnelle.

    Avant tout je vous souhaite à tous une joyeuse année 2009 malgré l'actualité qui n'est pas au beau fixe.

    Je suis certain que nous arriverons à trouver un coin de bonheur dans ce monde de brutes! ;)

    Je me présente:
    Je m'appelle Nicolas Surian (aka NairuS).

    Je suis développeur informatique depuis plus de 2 ans et je travaille pour une société de création d'outils de recrutement en vidéo JobinLive.

    Je suis Free-Lance depuis octobre 2008 (Il était temps).

    Mes domaines de compétences sont assez variés.

    En matière de développement:

    •  J'ai un niveau expérimenté : PHP, CakePHP, AMFPHP, MySQL
    •  Je suis débutant en Flash, AS2, AS3, Vegas, FMS, IoC


    J'ai également des compétences en Administration serveur :

    • Installation et configuration d'un serveur LAMP avec une distribution Ubuntu 8.04 serveur,
    • Installation et configuration d'openERP avec son client web eTiny,
    • Installation et configuration de trac et subversion.

    Le but de mon blog est aussi de traiter de sujet assez divers comme :

    • Des tutos de mise en pratique (ex. l'installation et la configuration de SVN / TRAC )
    • Des résolutions de problèmes (ex. Problème d'envoie de mail)
    • Des news en matière de nouvelles technologies (ex. Google Analytics pour Flash)



    Mais avant tout, je tiens à remercier eKameleon, mon mentor et ami, qui m'a aidé et poussé à créer mon identité sur la toile.

    Vous pouvez aussi voir mon profil de développeur sur Ohloh et LinkIn.

    A bientôt sur mon blog!

    Nairus ;)