Successfully reported this slideshow.
Your SlideShare is downloading. ×

Fare con Zend Framework 2 ciò che facevo con ZF1

Fare con Zend Framework 2 ciò che facevo con ZF1

Download to read offline

Introduzione a Zend Framework 2 per chi proviene da Zend Framework 1, tenuta allo Zend Framework Day di Milano del 01/02/2013. Introduzione alle nuove caratteristiche e pattern architetturali di ZF2

Zend Framework 2 non è l'evoluzione di ZF, ma un progetto nuovo: il codice è stato riscritto e poche sono le parti in comune con la versione precedente. Lo sviluppatore abituato a ZF1 non ha vita facile, ed è fondato il timore di dover imparare tutto da capo. In questo talk vediamo come cambiare le vecchie abitudini di sviluppatori ZF1, per sfruttare al meglio le potenzialità del nuovo strumento. Attraverso esempi concreti, in cui vedremo all'opera i nuovi pattern e le best practice, mostriamo come - partendo con il piede giusto - il passaggio a ZF2 possa essere meno traumatico del previsto. Il talk è orientato soprattutto a chi già conosce ZF1, ma gli argomenti affrontati possono essere utili anche a chi si avvicina a ZF per la prima volta.

Introduzione a Zend Framework 2 per chi proviene da Zend Framework 1, tenuta allo Zend Framework Day di Milano del 01/02/2013. Introduzione alle nuove caratteristiche e pattern architetturali di ZF2

Zend Framework 2 non è l'evoluzione di ZF, ma un progetto nuovo: il codice è stato riscritto e poche sono le parti in comune con la versione precedente. Lo sviluppatore abituato a ZF1 non ha vita facile, ed è fondato il timore di dover imparare tutto da capo. In questo talk vediamo come cambiare le vecchie abitudini di sviluppatori ZF1, per sfruttare al meglio le potenzialità del nuovo strumento. Attraverso esempi concreti, in cui vedremo all'opera i nuovi pattern e le best practice, mostriamo come - partendo con il piede giusto - il passaggio a ZF2 possa essere meno traumatico del previsto. Il talk è orientato soprattutto a chi già conosce ZF1, ma gli argomenti affrontati possono essere utili anche a chi si avvicina a ZF per la prima volta.

More Related Content

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

Fare con Zend Framework 2 ciò che facevo con ZF1

  1. 1. Zend Framework 2 Fare con ciò che facevo con ZF1 Zend Framework Day – Milano – 01/02/2013
  2. 2. @maraspin 2
  3. 3. http://www.mvassociati.it/ 3
  4. 4. http://friuli.grusp.org/ 4
  5. 5. brutto_esempio.php <?php echo '<html><body>'; $caldaia=$_GET['caldaia']; $mysqli = new mysqli("localhost", "user", "pwd", "mva"); $query = "SELECT * FROM cespiti WHERE id =".$caldaia; $result = $mysqli->query($query); while ($row = $result->fetch_assoc()) { ?><div><?php echo $row["nome, temperatura"]); ?></div> <?php } ?> <?php $result->free(); $mysqli->close(); echo '</html></body>'; ?>
  6. 6. IL “FENOMENO”
  7. 7. AGGIORNAMENTO
  8. 8. 9
  9. 9. LA SEGRETARIA
  10. 10. UFF. TECNICO
  11. 11. PRODUZIONE
  12. 12. ACCOUNT
  13. 13. Com’è strutturata l’azienda? 14
  14. 14. Che in un’applicazione… 15
  15. 15. MVC 16
  16. 16. Flusso MVC 17
  17. 17. Routing 18
  18. 18. Interazione con il modello 19
  19. 19. Il layer di presentazione 20
  20. 20. Risultato all’utente 21
  21. 21. Cosa ci manca? 22
  22. 22. Vorremmo una situazione così 23
  23. 23. Disaccoppiamento 24
  24. 24. I MODULI
  25. 25. Permettono questo 26
  26. 26. 27
  27. 27. Come installare ZF2? 28
  28. 28. THE SKELETON APP
  29. 29. Cloning git clone git://github.com/zendframework/ZendSkeletonApplication.git 30
  30. 30. 31
  31. 31. composer.json { "name": "zendframework/skeleton-application", "description": "Skeleton Application for ZF2", "license": "BSD-3-Clause", "keywords": [ "framework", "zf2" ], "homepage": "http://framework.zend.com/", "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.*" } }
  32. 32. composer.json { "name": "zendframework/skeleton-application", "description": "Skeleton Application for ZF2", "license": "BSD-3-Clause", "keywords": [ "framework", "zf2" ], "homepage": "http://framework.zend.com/", "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.*" } }
  33. 33. Installazione cd ZendSkeletonApplication php composer.phar install 34
  34. 34. Installazione cd ZendSkeletonApplication php composer.phar install > Installing zendframework/zendframework (dev-master) 35
  35. 35. Come impostare il progetto? 36
  36. 36. Struttura delle Cartelle 37
  37. 37. Struttura delle Cartelle Configurazione Applicazione 38
  38. 38. Struttura delle Cartelle Misc (cache, uploads, …) 39
  39. 39. Struttura delle Cartelle Moduli 40
  40. 40. Struttura delle Cartelle Moduli 41
  41. 41. Namespace Module.php Modulo 42
  42. 42. Struttura delle Cartelle Modulo Application 43
  43. 43. Struttura delle Cartelle Parte pubblica 44
  44. 44. DOCUMENT ROOT
  45. 45. DOCUMENT ROOT
  46. 46. Come funziona il bootstrap? 48
  47. 47. index.php <?php /** * This makes our life easier when dealing with paths. Everything is * relative to the application root now. */ chdir(dirname(__DIR__)); // Setup autoloading require 'init_autoloader.php'; // Run the application! ZendMvcApplication::init(require 'config/application.config.php')->run();
  48. 48. Configurazione config/ application.config.php 50
  49. 49. config/application.config.php <?php return array( 'modules' => array( 'Application', ), [...] ); Elenco dei moduli attivati
  50. 50. Configurazione 52
  51. 51. Module.config.php <?php [...] return array( 'router' => array('routes' => array([…]), ), 'service_manager' => array( 'factories' => array( 'translator' => ZendI18nTranslatorTranslatorServiceFactory', ),), [...] 'controllers' => array( 'invokables' => array( 'ApplicationControllerIndex‘=>'ApplicationControllerIndexController' ),), 'view_manager' => array( [...] ), );
  52. 52. Configurazione 54
  53. 53. Module.php <?php [...] class Module implements AutoloaderProviderInterface, ConfigProviderInterface, ServiceProviderInterface { public function getAutoloaderConfig() {[...]} public function getConfig($env = null) { return include __DIR__ . '/config/module.config.php'; } public function getControllerPluginConfig() {[...]} public function getViewHelperConfig() {[...]} public function getServiceConfig() {[...]} }
  54. 54. Configurazione 56
  55. 55. Configurazione 57
  56. 56. BOOTSTRAP
  57. 57. ROUTING
  58. 58. Dall’url al codice return array( […] 'routes'=> array( […] 'route' => '/sostituiscilampadina', 'defaults' => array( 'controller' => 'ApplicationControllerElettricista', 'action' => ‘sostlampadina', ) […] ), 'controllers' => array( 'invokables' => array( 'AppControllerElettricista' => 'AppControllerElettricistaController', ), ), )
  59. 59. Dall’url al codice return array( […] 'routes'=> array( […] 'route' => '/sostituiscilampadina', 'defaults' => array( 'controller' => 'ApplicationControllerElettricista', 'action' => ‘sostlampadina', ) […] ), 'controllers' => array( 'invokables' => array( 'AppControllerElettricista' => 'AppControllerElettricistaController', ), ), )
  60. 60. Dall’url al codice return array( […] 'routes'=> array( […] 'route' => '/sostituiscilampadina', 'defaults' => array( 'controller' => 'ApplicationControllerElettricista', 'action' => ‘sostlampadina', ) […] ), 'controllers' => array( 'invokables' => array( 'AppControllerElettricista' => 'AppControllerElettricistaController', ), ), )
  61. 61. CONVENTION VS CONFIGURATION
  62. 62. Come sono fatti i controller? 64
  63. 63. Controller Semplice in ZF2 <?php namespace ApplicationController; use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IndexController extends AbstractActionController { public function indexAction() { $timestamp = time(); return new ViewModel(array( 'timestamp' => $timestamp )); } }
  64. 64. Action <?php namespace ApplicationController; use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IndexController extends AbstractActionController { public function indexAction() { $timestamp = time(); return new ViewModel(array( 'timestamp' => $timestamp )); } }
  65. 65. Come interagisco con il model? 67
  66. 66. Controller ZF1 […] class IdraulicoController extends Zend_Controller_Action { public function installaCaldaia () { $operatore = new Operatore(); $esito = $operatore->installaCaldaia(); $this->view->esito =$esito; }
  67. 67. Scendiamo nel dettaglio […] class IdraulicoController extends Zend_Controller_Action { public function installaCaldaia () { $operatore = new Operatore(); $esito = $operatore->installaCaldaia(); $this->view->esito =$esito; }
  68. 68. Com’è fatto questo oggetto? […] Class Operatore { public function __constructor () { $this->caldaia = new Caldaia(); } public function getCaldaia() { return $this->caldaia; } public function installaCaldaia() { // Fai qualcosa con $this->caldaia } }
  69. 69. Dipendenza. Siamo vincolati! […] Class Operatore { public function __constructor () { $this->caldaia = new Caldaia(); } public function getCaldaia() { return $this->caldaia; } public function installaCaldaia() { // Fai qualcosa con $this->caldaia } }
  70. 70. “IL PRINCIPIO DEL GARZONE”
  71. 71. Siamo vincolati […] Class Operatore { public function __constructor (Caldaia $caldaia) { $this->caldaia = $caldaia; } public function getCaldaia() { return $this->caldaia; } public function installaCaldaia() { // Fai qualcosa con $this->caldaia } }
  72. 72. Siamo vincolati […] Class Operatore { public function __constructor (Caldaia $caldaia) { $this->caldaia = $caldaia; } public function getCaldaia() { return $this->caldaia; } public function installaCaldaia() { // Fai qualcosa con $this->caldaia } }
  73. 73. Test operatore […] class OperatoreTest extends TestCase { function testInstallaCaldaia() { $caldaia = mockCaldaia(); $operatore = new Operatore($caldaia); $esito = $operatore->installaCaldaia(); $this->assertTrue($esito); } }
  74. 74. Da… 76
  75. 75. A… 77
  76. 76. INVERSIONE DI CONTROLLO
  77. 77. Factory operatore […] class OperatoreFactory { public static function create ($nome) { $caldaia = new Caldaia(); $operatore = new Operatore($caldaia); return $operatore; } }
  78. 78. Controller ZF1 […] class IdraulicoController extends Zend_Controller_Action { public function installaCaldaia () { $I_operatore = OperatoreFactory::create(); $esito = $I_operatore->installaCaldaia(); $this->view->esito =$esito; }
  79. 79. ZF2 - Il Service Manager […] class IdraulicoController extends AbstractActionController { public function installaCaldaia () { $operatore = $this->serviceLocator->get(‘operatore’); $esito = $I_operatore->installaCaldaia(); return new ViewModel(‘operatore’ => $operatore, ‘esito’ => $esito); }
  80. 80. Riconsideriamo la configurazione return array( ... 'controllers' => array( ‘factories' => array( 'AppControllerIdraulico' => 'AppControllerIdraulicoControllerFactory', ),), ‘service_manager’ => ‘factories' => array( ‘operatore' => ‘MVAServicesOperatoreFactory', ), ‘invokables' => array( ‘caldaia' => ‘MVAServicesCaldaia', ), ‘aliases' => array( ‘attrezzi.martelloPneumatico' => ‘attrezzi.martello', ), )
  81. 81. Factories return array( ... 'controllers' => array( ‘factories' => array( 'AppControllerIdraulico' => 'AppControllerIdraulicoControllerFactory', ),), ‘service_manager’ => ‘factories' => array( ‘operatore' => ‘MVAServicesOperatoreFactory', ), ‘invokables' => array( ‘caldaia' => ‘MVAServicesCaldaia', ), ‘aliases' => array( ‘attrezzi.martelloPneumatico' => ‘attrezzi.martello', ), )
  82. 82. Invokables return array( ... 'controllers' => array( ‘factories' => array( 'AppControllerIdraulico' => 'AppControllerIdraulicoControllerFactory', ),), ‘service_manager’ => ‘factories' => array( ‘operatore' => ‘MVAServicesOperatoreFactory', ), ‘invokables' => array( ‘caldaia' => ‘MVAServicesCaldaia', ), ‘aliases' => array( ‘attrezzi.martelloPneumatico' => ‘attrezzi.martello', ), )
  83. 83. LAZY REGISTRY, ON STEROIDS
  84. 84. Service Locator
  85. 85. SE FATTO CON REGISTRY
  86. 86. Service Locator
  87. 87. Service Locator
  88. 88. Uso di Alias return array( ... 'controllers' => array( ‘factories' => array( 'AppControllerIdraulico' => 'AppControllerIdraulicoControllerFactory', ), ), ‘service_manager’ => ‘invokables' => array( ‘attrezzi.martello' => 'AppServicesMartello', ‘attrezzi.martelloPneumatico‘ => 'AppServicesMartelloPneumatico', ), )
  89. 89. Uso di Alias return array( ... 'controllers' => array( ‘factories' => array( 'AppControllerIdraulico' => 'AppControllerIdraulicoControllerFactory', ), ), ‘service_manager’ => […] ‘invokables' => array( ‘attrezzi.martello' => 'AppServicesMartello', ‘attrezzi.supermartello' => 'AppServicesMartello', ), ‘aliases' => array( ‘attrezzi.martelloPneumatico' => ‘attrezzi.supermartello', ), )
  90. 90. I diversi service managers
  91. 91. Diagramma service managers
  92. 92. Peering
  93. 93. Uso del Service Locator? […] class Termostato implements ServiceLocatorAwareInterface { […] public function getTemperatura() { $sm = $this->getServiceLocator(); $sensore = $sm->get(‘AziendaModelTermoSensore'); return $sensore->getTemperatura(); } public function setServiceLocator(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; } public function getServiceLocator() { return $this->serviceLocator; } }
  94. 94. Abuso del Service Locator […] class Termostato implements ServiceLocatorAwareInterface { […] public function getTemperatura() { $sm = $this->getServiceLocator(); $sensore = $sm->get(‘AziendaModelTermoSensore'); return $sensore->getTemperatura(); } public function setServiceLocator(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; } public function getServiceLocator() { return $this->serviceLocator; } }
  95. 95. ATTENZIONE!
  96. 96. Abuso del Service Locator […] class Termostato implements ServiceLocatorAwareInterface { […] public function getTemperatura() { $sm = $this->getServiceLocator(); $sensore = $sm->get(‘AziendaModelTermoSensore'); return $sensore->getTemperatura(); } public function setServiceLocator(ServiceLocatorInterface $serviceLocator) { $this->serviceLocator = $serviceLocator; } public function getServiceLocator() { return $this->serviceLocator; } }
  97. 97. Factory per il Controller […] Class IdraulicoControllerFactory implements FactoryInterface { public static function createService(ServiceLocatorInterface $services) { $sm = $services->getServiceLocator(); $operatore = $sm->get(‘Operatore'); return new IdraulicoController($operatore); } […] } ?>
  98. 98. DEPENDENCY INJECTION http://ocramius.github.com/blog/zend-framework-2-controllers-and-dependency-injection-with-zend-di/
  99. 99. E per le funzioni di supporto? 101
  100. 100. Scambio di messaggi class IdraulicoController { public function installaCaldaiaAction() { $operatore = $this->serviceLocator->get(‘operatore’); $esito = $I_operatore->installaCaldaia(); $I_logger = new Logger(); $I_logger->log(‘Installazione Caldaia’); return new ViewModel(‘operatore’ => $operatore, ‘esito’ => $esito); } }
  101. 101. Mi trovo nel posto giusto? class IdraulicoController { public function installaCaldaiaAction() { $operatore = $this->serviceLocator->get(‘operatore’); $esito = $I_operatore->installaCaldaia(); $I_logger = new Logger(); $I_logger->log(‘Installazione Caldaia’); return new ViewModel(‘operatore’ => $operatore, ‘esito’ => $esito); } }
  102. 102. Messaggio Diretto
  103. 103. CROSS CUTTING CONCERNS
  104. 104. Problema di estendibilità $s_msg = ‘Installazione Caldaia’; $I_logger = new Logger(); $I_logger->log($s_msg); $I_mailer = new Mailer(); $I_mailer->mail($s_msg); […]
  105. 105. Event Manager Class Segretaria { public function onFinitoIntervento() { /* Occupati della Fatturazione */ } }
  106. 106. Event Manager Class IdraulicoFactory { createService(ServiceLocatorInterface $serviceLocator) { $idraulico = new Idraulico (); $idraulico ->setEventManager( $serviceLocator->get('eventManager') ); $segretaria = $serviceLocator->get(‘segretaria'); $idraulico->getEventManager()->attach( ‘finitointervento', array($segretaria, ‘onFinitoIntervento') ); return $idraulico; } }
  107. 107. Event Manager //nella classe che scatena l’evento... class Idraulico { public function installaCaldaia () { /* Codice Installazione Caldaia */ $this->eventManager ->trigger(‘finitointervento‘, $this, array(‘azione’ => ‘Tutto OK!’) ); } }
  108. 108. Event Manager
  109. 109. OBSERVER
  110. 110. Event Manager
  111. 111. Come interagisco con il DB? 113
  112. 112. ZENDDB
  113. 113. 115
  114. 114. Come uso controller plugin? 116
  115. 115. Esempio con controller plugin <?php namespace ApplicationController; use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IndexController extends AbstractActionController { public function redirectAction() { $this->redirect()->toUrl(‘http://www.zfday.it’); } }
  116. 116. Come recupero parametri? 118
  117. 117. Factory per il Controller […] use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IndexController extends AbstractActionController { public function scriviNomeAction() { $nome = $this->getEvent() ->getRouteMatch()->getParam('slug'); return (‘nome’ => $nome); } }
  118. 118. E se ho funzionalità ripetute? 120
  119. 119. init() <?php class IdraulicoController extends Zend_Controller_Action { public function init () { $this->view->azienda = $this->getParam(‘azienda’); } }
  120. 120. Ma non ho ancora l’EM! […] use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IndexController extends AbstractActionController { public function __construct() { $this->azienda = $this->getEvent() ->getRouteMatch()->getParam(‘azienda'); } }
  121. 121. Sulla Dependency Injection… […] // Sul costruttore public function __construct($dipendenza) { $this->dipendenza = $dipendenza; } // Con setters Public function __construct() { } Public function setDipendenza() { $this->dipendenza = $dipendenza; }
  122. 122. Agisco sul setter […] use ZendMvcControllerAbstractActionController; use ZendViewModelViewModel; class IdraulicoController extends AbstractActionController { public function setEventManager(EventManagerInterface $events) { parent::setEventManager($events); $controller = $this; $events->attach('dispatch', function ($e) use ($controller) { $this->name = $controller->params()->fromRoute(‘azienda', ‘Sconosciuta'); } }} Vedasi anche il post di M. W. O’Phinney: http://www.mwop.net/blog/2012-07-30-the-new-init.html
  123. 123. Come funzionano le viste? 125
  124. 124. Il layer di presentazione
  125. 125. View //view index/index.phtml <div class="row"> <!-- Including header partial --> <?php echo $this->partial('partials/header.phtml', array()); ?> <!-- Stampiamo la data --> <h1>Actual time is: <?php echo $this->timestamp;?></h1> </div>
  126. 126. View con helper //usando il view helper printData //view index/index.phtml <div class="row"> <!-- Including header partial --> <?php echo $this->partial('partials/header.phtml', array()); ?> <!-- Stampiamo la data --> <h1>Actual time is: <?php echo $this->printData($this->timestamp);?> </h1> </div>
  127. 127. Helper //Creando un viewHelper per formattare la data // Va registrato tra gli invokables del SM dei ViewHelpers <?php namespace ApplicationViewHelper; class PrintData extends ZendViewHelperAbstractHelper { public function __invoke($timestamp) { $date = new DateTime(); $date->setTimestamp($timestamp); $result = $date->format('d-m-Y H:i'); return $result; } }
  128. 128. Come funzionano le form? 130
  129. 129. Creando una form //Creiamo il file ContactForm dentro nostro modulo Application/src/Form <?php namespace ApplicationForm; use ZendFormElement; use ZendFormForm; class ContactForm extends Form { public function __construct() { parent::__construct(); // … } //... }
  130. 130. Form: aggiungiamo i campi public function init() { $this->setName('contact'); $this->add(array( 'name' => ‘emailMittente', 'type' => 'ZendFormElementText', 'options' => array( 'label' => 'From:', ), ) $this->add(array( 'name' => 'Send', 'type' => 'ZendFormElementSubmit', 'attributes' => array( 'value' => 'Send', ),); //... } }
  131. 131. La Form Form
  132. 132. Gli InputFilter
  133. 133. Validation: inputFilter //creiamo l'imput filter contactFilter.php dentro Application/src/Form <?php namespace ApplicationForm; use ZendInputFilterInputFilter; use ZendValidatorHostname as HostnameValidator; class ContactFilter extends InputFilter { public function __construct() { //aggiungiamo i filtri } }
  134. 134. InputFilter: filter & validation //Esempio filtering(trim) + email validation with custom message $this->add(array( 'name' => ‘emailMittente', 'required' => true, 'filters' => array( array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'EmailAddress', 'options' => array( 'messages' => array( ZendValidatorEmailAddress::INVALID => 'L'indirizzo email inserito non è valido.'), ) ) ) ));
  135. 135. Controller: Usando la form namespace ApplicationController; use ZendMvcControllerAbstractActionController; use ApplicationFormContact; use ApplicationFormContactFilter; class IndexController extends AbstractActionController { public function contactAction() { $form = new Contact(); $filter = new ContactFilter(); $form->setInputFilter($filter); return new ViewModel(array( 'form' => $form ); } }
  136. 136. View: Usando la form <?php $form = $this->form; $form->setAttribute('action', $this->url('contact')); $form->prepare(); echo $this->form()->openTag($form); ?> <dl class="contact_form"> <dt><?php echo $this->formLabel($form->get('from')); ?></dt> <dd><?php echo $this->formInput($form->get('from')); echo $this->formElementErrors($form->get('from')); ?></dd> <!-- ... --> </dl> <?php echo $this->form()->closeTag($form) ?>;
  137. 137. Una form più filtri
  138. 138. Come posso proseguire? 140
  139. 139. Per Approfondire 141
  140. 140. Per Approfondire 142
  141. 141. Sporcati le mani: https://packages.zendframework.com/docs/latest/manual/en/user-guide/overview.html 143
  142. 142. Segui i migliori eventi http://www.phpday.it 144
  143. 143. In sintesi 1. Maggior enfasi sul riuso. Tenere a mente che ci sono i Moduli 2. Su ZF2 si segue maggiormente un approccio orientato alla configurazione piuttosto che convenzione 3. Lo strumento cerca di favorire il disaccoppiamento (EventManager) e l’inversione di controllo (ServiceManager, DI) 145
  144. 144. Grazie per l’attenzione Stefano Maraspin @maraspin s.maraspin@mvassociati.it
  145. 145. DOMANDE?
  146. 146. Photo Credits • http://www.flickr.com/photos/cclark395/7671665642/ • http://www.flickr.com/photos/thompsonrivers/8072087088/ • http://www.flickr.com/photos/ter-burg/5807937726/ • http://www.flickr.com/photos/cimmyt/7798631622/ • http://www.flickr.com/photos/reckless_sniper/6568298617/ • http://www.flickr.com/photos/fil/144232588/ • http://www.flickr.com/photos/ponchopenguin/3262049873/ • http://www.flickr.com/photos/hugosimmelink/1791812548/ • http://www.flickr.com/photos/calsidyrose/4925267732/ • http://www.flickr.com/photos/29233640@N07/8412347937/ • http://www.flickr.com/photos/stungeye/2774997317/ • http://www.flickr.com/photos/urbanwoodswalker/4375401057/ • http://www.flickr.com/photos/adelaide_archivist/3262863212/ • http://www.flickr.com/photos/syslfrog/172945973/ • http://www.flickr.com/photos/tognum/6279595321/ • http://www.flickr.com/photos/dominichargreaves/2825624738/ • http://www.flickr.com/photos/lstcaress/502606063/ • http://www.flickr.com/photos/a-g/2128462119/ • http://www.flickr.com/photos/see-through-the-eye-of-g/4087838437/ 148
  147. 147. Stefano Maraspin @maraspin s.maraspin@mvassociati.it

×