Aggiungere una estensione php a Zend server: nella versione 6 per Mac OS X manca Phar

Facendo l’aggiornamento da Zend server 5.6.0 al nuovo fiammante 6.0.0 con PHP 5.3.21 mi sono accorto spiacevolmente che il supporto a Phar non era più attivo. Vado quindi sul pannello a cercare questa ormai indispensabile estensione (leggi: come posso vivere senza composer e idephix?) e non la trovo!
Cerca e spulcia sul web ma del supporto non c’è traccia. Mentre per Linux si può scaricare il pacchetto, per Mac OS X si può solo compilare ex-novo e quindi…

Si scarica il sorgente di PHP 5.3.21 e si compila l’estensione

cd
tar xjf path/dove/lo/hai/scaricato/php-5.3.21.tar.bz2
cd php-5.3.21/ext/phar
phpize
MACOSX_DEPLOYMENT_TARGET=10.8 CFLAGS="-mmacosx-version-min=10.8 -arch x86_64" LDFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" ./configure
make

Si copia nella directory corretta

sudo cp modules/phar.so /usr/local/zend/lib/php_extensions/

La si aggiunge in php.ini tra le altre estensioni oppure:

sudo echo 'extension="phar.so"' >>/usr/local/zend/etc/php.ini

Ed ora…

$ php -i|grep Phar
Phar
Phar: PHP Archive support => enabled
Phar EXT version => 2.0.1
Phar API version => 1.1.1
Phar-based phar archives => enabled
Phar based on pear/PHP_Archive, original concept by Davey Shafik.
Phar fully realized by Gregory Beaver and Marcus Boerger.

Una volta che sai come fare è veramente facile, per fortuna che me solo segnato per la prossima volta :D

Buon php-coding

Doctrine2 event listener: postPersist o non postPersist?

Oggi mi sono imbattuto in un problema apparentemente semplice che mi ha dato un po’ di filo da torcere: il postPersist.
Il problema è abbastanza comune: devo aggiornare alcuni oggetti di una collezione quando uno di questi viene modificato o inserito con particolari caratteristiche. Sto sviluppando un applicazione con Sf2 e ho configurato il config.yml per gestire l’evento postPersist e postUpdate di Doctrine2 come servizio.

services:
    listener.entity.myEntity
:
        class
: Ideato\MyBundle\Listener\Entity\MyEntityListener
        tags
:
            - { name
: doctrine.event_listener, event: postPersist }
            - { name
: doctrine.event_listener, event: postUpdate }

Quindi creo la classe per il listener

<?php
namespace Ideato\MyBundle\Listener\Entity;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Ideato\MyBundle\Entity\MyEntity;

class MyEntityListener
{
    public function postUpdate(LifecycleEventArgs $args)
    {
        $this->postPersist($args);
    }

    public function postPersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        if ($entity instanceof MyEntity) {
            $em = $args->getEntityManager();
            // query che interpellano il repository dell'entità e alcune relazioni
        }
    }
}

Oibò banale! Fatto in millllioni di altri posti; lancio i test: ROSSI!
Azzzzzpiterina cosa c’è di diverso? Risposta: le relazioni!
Ebbene si, nonostante il cascading sul persit e l’update, l’evento postPersist/Update viene invocato immediatamente dopo il persist o l’update dell’entità ma prima del persist delle relazioni. Se questo in relazioni oneToOne, oneToMany o manyToOne non provochi problemi di sorta, per le manyToMany sì, sopratutto nel caso in cui la condizione che serve è proprio sulla relazione. Infatti nelle relazioni oneTo* possiamo spostare il listener “sull’altra” entità che viene persistita. Per quanto riguarda le manyToMany, non c’è una entità alla quale si possa agganciare il listener.
Soluzione: agganciarsi al postFlush. Questo è invocato quando tutto è andato a buon fine e solo se c’è stato qualcosa da memorizzare (è cambiata qualche proprietà da persistere), altrimenti non viene invocato. Una limitazione che nel mio caso rafforza la correttezza dell’invocazione.

Human vs Bot: captcha o altro?

In questi giorni mi sono chiesto se il catpcha fosse veramente la soluzione al problema Bot per SocialCibus. Così mi sono letto diversi articoli in rete. Alcuni di questi parlavano di un 3-10% di utenti che, quando vedono un captcha, lasciano perdere la registrazione, altri abbandonano solo dopo aver sbagliato una o due volte l’inserimento del testo.
Posso tenere fuori i bot e dentro gli esseri umani senza captcha? E’ la domanda che si sono posti in tanti.
Continua a leggere

Testare pagine web con versioni differenti di Internet Explorer

Spesso ho la necessità di testare alcune pagine web con differenti versioni di IE, a volte per la grafica ma più spesso per verificare le funzionalità Javascript sulle varie versioni di Internet Explorer.
Fin’ora ho sempre pensato che lo strumento migliore fosse IETester anche se sapevo che non era sempre coerente.
Oggi ho letto questa guida sul cross browser testing con IE. Spettacolo!
Il post è un po’ lunghino ma ne vale la pena leggerlo. Non racconta cose mirabolanti ma descrive lo stato dell’arte e mi ha rivelato un tool che non conoscevo: Utilu IE Collection [Non è un errore si chiama proprio Utilu]
Ma non voglio svelarvi il finale :)
Buona lettura.

Notifiche con Doctrine: ereditarietà con relazioni diverse per i figli

Stavo cercando di creare un sistema di notifiche (tipo wall di FB) di ciò che accade agli utenti del mio nuovo sito di ricette e mi sono imbattuto in un problema: ereditarietà con Doctrine e relazioni diverse per ogni figlio.
Premetto che il sito è realizzato con Sf 1.4 e Doctrine 1.2.
Accantonato per il momento l’utilizzo di database documentali e varianti, rimanendo nel mondo del relazionale, per l’implementazione del sistema di notifiche ho fatto il seguente ragionamento:
ci sono principalmente due modi per memorizzare le informazioni su “cosa sta accadendo” (es. nuova ricetta di un amico, commento ad una mia ricetta, follow, like, etc.):

  • Leggere ogni volta tutti gli inserimenti dalle rispettive tabelle (Recipe, Comment, Follow, Like)

    • Pro: non devo aggiungere modelli al db
    • Contro: la lettura delle informazioni è lentisssssima (devo leggere tutte le tabelle), la computazione (merge e ordinamento) è impegnativa e aumenta al crescere delle informazioni memorizzate
  • Scrivere e leggere su una tabella che raggruppa le notifiche.
    • Pro: il fetch è velocissimo, posso mantenere la distinzione delle tipologie di notifica, non è necessaria computazione aggiuntiva oltre al fetch per merge e ordinamento
    • Contro: devo realizzare e gestire un repository per le notifiche, la mappatura ai vari tipi di notifica non è banale

Naturalmente ho scelto la seconda, che sicuramente è la soluzione più pulita e performante.
Di seguito lo schema (semplificato) dei modelli da “notificare”.

Recipe:
  id
:
  user_id
:
  title
:
 ...
relations
:
    User
: { class: sfGuardUser, ... }

Comment
:
  id
:
  user_id
:
  recipe_id
:
  comment
:
 ...
relations
:
    User
: { class: sfGuardUser, ... }
    Recipe
: { class: Recipe, ... }

Follow
:
  user_id
:
  followed_user_id
:
relations
:
    User
: { class: sfGuardUser, ... }
    FollowedUser
: { class: sfGuardUser, ... }

Like
:
  id
:
  user_id
:
  recipe_id
:
 ...
relations
:
    User
: { class: sfGuardUser, ... }
    Recipe
: { class: Recipe, ... }

Ed eccoci a modellare le notifice: per realizzare la mappatura alle varie tipologie di notifica ho deciso di utilizzare l’ereditarietà con aggregazione di colonne di Doctrine.

Notification:
  columns
:
    user_id
:
    dest_user_id
:
    subject_model
:
    subject_id
:
   ...
  relations
:
    User
: { class: sfGuardUser ... }
    DestUser
: { class: sfGuardUser, ... }
  indexes
:
    subject_id_index
:
      fields
: [subject_id]

NotificationComment
:
  inheritance
: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 1 }
NotificationRecipe
:
  inheritance
: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 2 }
NotificationFollow
:
  inheritance
: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 3 }
NotificationLike
:
  inheritance
: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 4 }

Bene, ora ho quello che mi serve… o quasi. Considerando che subject_id sia l’id del soggetto della notifica (es. Ricetta o Commento) mi serve una relazione con il repository del soggetto stesso, cosicchè possa avere direttamente l’oggetto relativo alla notifica:

$notification_recipe = Doctrine::getTable('NotificationRecipe')->find(1);
$recipe = $notification_recipe->getRecipe();

Come aggiungo le relazioni? Ho provato con le relazioni “classiche”:

NotificationComment:
  inheritance: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 1 }
  relations:
    Comment: { class: Comment, ... }
NotificationRecipe:
  inheritance: { extends: Notification, type: column_aggregation, keyField: type, keyValue: 2 }
  relations:
    Recipe: { class: Recipe, ... }

Funziona! Ma… il problema è che alla creazione del database le relazioni le crea… TUTTE nella stessa tabella, quindi abbiamo Notification che ha come constraint tutte le relazioni definite, con Recipe, Comment, Like e Follow.
Il che non funziona affatto, poichè ad ogni inserimento avremo errori di chiavi importate inesistenti nella tabella Notification. Sigh!
Ricominciamo ed eliminiamo le relazioni dallo schema.yml.
Cercando in rete ho trovato un attributo che fa al caso mio: Doctrine::EXPORT_CONSTRAINTS
Quindi ho modificato i modelli dicendo di non esportare le constraint:

class NotificationRecipe extends BaseNotificationRecipe
{
  public function setUp()
  {
      parent::setUp();
      $this->hasOne('Recipe', array(
           'local' => 'subject_id',
           'foreign' => 'id',
           'onDelete' => 'cascade'));
      $this->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL ^ Doctrine::EXPORT_CONSTRAINTS);
  }
}

Si può fare anche direttamente nello schema.yml ma non ho indagato otre, perchè già così… FUNZIONA!!!

Buon divertimento

Installare estensioni PECL su Mac OsX e Zend Server

Per evitare errori del tipo:

PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/local/zend/lib/php_extensions/XXXX.so' - dlopen(/usr/local/zend/lib/php_extensions/XXXX.so, 9): no suitable image found.  Did find:
    /usr/local/zend/lib/php_extensions/XXXX.so: mach-o, but wrong architecture in Unknown on line 0

Un link vale più di mille parole!
http://serverfault.com/questions/138046/how-to-compile-php-pecl-apd-for-mamp-snow-leopard

K3a.it: un mashup in PHP (blog, flickr, twitter) con HTML5 e CSS3

Prendendo spunto dal mio amico Francesco mi vorrei inoltrare nel creare una pagina di mashup dei miei account dei servizi maggiormente utilizzati: twitter, flickr e blog. Visto che non inserisco contenuti da mesi in nessuno dei tre, voi direte, “cosa lo fai a fare?”. Per ripassare come si può realizzare qualcosa di semplice in PHP e come si può organizzare un progetto (semplice) da zero utilizzando le “migliori pratiche” imparando qualcosa di nuovo. Continua a leggere

Doctrine migrate: quando ci si accorge di essere in ritardo

A volte può capitare di essere pronti per il deploy di una applicazione quando ci accorgiamo di aver dimenticato di creare gli script per aggiornare il database in produzione.
Con Symfony, Doctrine e un sistema di versioning è possibile rimediare molto facilmente.
Questa è la mia ricetta:

  • Recuperare la versione di
    config/doctrine/schema.yml

    che rappresenta il database in produzione (verosimilmente il file che è adesso in produzione ;) oppure con il sistema di versioning)

  • Lanciare
    ./symfony doctrine:build --all
  • Copiare l’attuale file
    config/doctrine/schema.yml

    in un posto sicuro

  • Sostituire il file
    config/doctrine/schema.yml

    con la copia “vecchia” recuperata

  • Lanciare
    ./symfony doctrine:generate-migrations-diff

    (se necessario con il parametro env settato)

  • Aprire il file
    *version*.php

    appena creato dalla procedura in

    lib/migration/doctrine

    (se è la prima volta che migri ce n’è uno solo altrimenti apri solo l’ultimo)

  • Inverti il nome dei metodi “up()” e “down()”, cioè sostituisci il nome “up()” con “down()” e viceversa
  • Riporta lo
    schema.yml

    all’ultima versione

  • Ora puoi lanciare il task di migrazione
    ./symfony doctrine:migrate
  • Nel caso di errori di CHARSET o COLLATION nel lanciare il task precedente, verifica che nel file di migrazione che hai modificato precedentemente non siano presenti i parametri ‘charset’ e ‘collate’ come opzioni dei metodi createTable()

Buona migrazione a tutti.

Selenium RC Firefox vs Iceweasel in Debian

Se siete così pazzi da aver installato Debian sui vostri computer e rimanete fedeli alle sue politiche (anche con qualche strappo), vi troverete con questo errore lanciando i vostri test con Selenium RC

PHPUnit_Framework_Exception: Response from Selenium RC server for getNewBrowserSession(*firefox, http://localhost/).
Failed to start new browser session: Error while launching browser.

Questo semplicemente perchè… non avete effettivamente Firefox installato!
I trucco è aggiungere il path di Iceweasel nella dichiarazione del browser da utilizzare con Selenium. Quindi nei vostri test (se utilizzate PHP) sarà

$this->setBrowser("*firefox /usr/bin/iceweasel");

Oppure aggiungete il link

ln -s /usr/lib/iceweasel/iceweasel /usr/bin/firefox-bin

Buoni test a tutti!

Symfony: caricare helper dalle action

Talvolta è utile poter caricare alcuni helper dal controller o dal modello, per esempio mi è capitato di dover formattare una data o utilizzare alcuni dei miei helper direttamente dal controller.
Per far questo è sufficiente inserire il codice seguente nel metodo in cui si ha la necessità:


sfLoad::helpers(array('helper1', 'helper2', ...));

Buon coding.

UPDATE: per sf 1.4


$this->getContext()->getConfiguration()->loadHelpers('Partial');
$this->getContext()->getConfiguration()->loadHelpers(array('helper1', 'helper2', ...));