Graph databases in PHP @ PHPCon Poland 10-22-2011

5,713 views

Published on

Presentation given at the national PHP conference in Poland, in Kielce, October 2011, dealing with the introduction of graph databases in PHP, taking a practical look at OrientDB.

Published in: Technology

Graph databases in PHP @ PHPCon Poland 10-22-2011

  1. 1. GraphDB in PHP Alessandro Nadalin David Funaro 1domenica 23 ottobre 11
  2. 2. Agenda •Theory •When to use a graph? •Why graphDB? •The graphDB community •OrientDB •OrientDB in PHP •Demo 2domenica 23 ottobre 11
  3. 3. Essential (Theory) 3domenica 23 ottobre 11
  4. 4. Essential (Theory) G= Graph 3domenica 23 ottobre 11
  5. 5. Essential (Theory) G = (V, Vertex Graph 3domenica 23 ottobre 11
  6. 6. Essential (Theory) G = (V, Vertex Graph A 3domenica 23 ottobre 11
  7. 7. Essential (Theory) G = (V, E) Vertex Graph Edge A 3domenica 23 ottobre 11
  8. 8. Essential (Theory) G = (V, E) Vertex Graph Edge A 3domenica 23 ottobre 11
  9. 9. Binary Relation Hates A B Itchy Scratchy 4domenica 23 ottobre 11
  10. 10. Binary Relation Edge A B Vertex Vertex 4domenica 23 ottobre 11
  11. 11. Graph B E F A D G 5domenica 23 ottobre 11
  12. 12. Undirected Graph B E A D FExample: Friendship 6domenica 23 ottobre 11
  13. 13. Directed Edge A B Vertex Vertex 7domenica 23 ottobre 11
  14. 14. Directed Edge Edge A B Vertex Vertex 7domenica 23 ottobre 11
  15. 15. Directed Graph A B A F DExample: Followee 8domenica 23 ottobre 11
  16. 16. Path B E F A D G 9domenica 23 ottobre 11
  17. 17. Path A B D G E F 10domenica 23 ottobre 11
  18. 18. Graph -> GraphDB GraphDB is a database that use the Graph as its primary data structure 11domenica 23 ottobre 11
  19. 19. ... when to use a graph ?domenica 23 ottobre 11
  20. 20. Web in ’99 13domenica 23 ottobre 11
  21. 21. Web in 2005 14domenica 23 ottobre 11
  22. 22. The social web 15domenica 23 ottobre 11
  23. 23. Your data is a graph 16domenica 23 ottobre 11
  24. 24. a tree is a graph 17domenica 23 ottobre 11
  25. 25. parent_id is a graph 18domenica 23 ottobre 11
  26. 26. Recommendations lives in John type shows Mr Fun Cinema B Bean loca tion lik Rome es shows Cinema A location type Thriller Se7en s ho ws location Milan Cinema C 19domenica 23 ottobre 11
  27. 27. Recommendations lives in John type shows Mr Fun Cinema B Bean loca tion lik Rome es type shows Cinema A location ✓ x x Thriller Se7en s ho ws location Milan Cinema C 20domenica 23 ottobre 11
  28. 28. Recommendations lives in John type shows Mr Fun Cinema B Bean ✓ loca tion lik Rome es type shows Cinema A location ✓ x x Thriller Se7en s ho ws location Milan Cinema C 21domenica 23 ottobre 11
  29. 29. Recommendations lives in John type shows Mr Fun Cinema B ✓ Bean ✓ loca tion lik Rome es type shows Cinema A location ✓ x x Thriller Se7en s ho ws location Milan Cinema C 22domenica 23 ottobre 11
  30. 30. Recommendations lives in John xFun type Mr ✓ Bean shows Cinema B ✓ loca tion lik Rome es type shows Cinema A location ✓ x x Thriller Se7en s ho ws location Milan Cinema C 23domenica 23 ottobre 11
  31. 31. Recommendations lives in John x x x Fun type Mr Bean shows Cinema B loca tion lik Rome es type shows ✓ Cinema A location ✓ x x Thriller Se7en s ho ws location Milan Cinema C 24domenica 23 ottobre 11
  32. 32. Recommendations lives in John x x x Fun type Mr Bean shows Cinema B loca tion lik Rome es type shows ✓ Cinema A location ✓ ✓ shows x x Thriller Se7en location Milan Cinema C 25domenica 23 ottobre 11
  33. 33. Recommendations lives in John x x x Fun type Mr Bean shows Cinema B loca tion lik Rome es type shows ✓ Cinema A location ✓ ✓ shows x x ✓ Thriller Se7en location Milan Cinema C 26domenica 23 ottobre 11
  34. 34. Recommendations lives in John x x x Fun type Mr Bean shows Cinema B loca tion lik ✓ Rome es type shows ✓ Cinema A location ✓ ✓ shows x x ✓ Thriller Se7en location Milan Cinema C 27domenica 23 ottobre 11
  35. 35. Solve decision problemsdomenica 23 ottobre 11
  36. 36. Maximum flowdomenica 23 ottobre 11
  37. 37. domenica 23 ottobre 11
  38. 38. Given a dataset, calculate how to best organize it maximum flowdomenica 23 ottobre 11
  39. 39. travelling salesman problemdomenica 23 ottobre 11
  40. 40. The pizza guy needs to deliver on A, B,C.domenica 23 ottobre 11
  41. 41. Decision base on distance, traffic, time and so on.domenica 23 ottobre 11
  42. 42. Shortest pathdomenica 23 ottobre 11
  43. 43. Identify "special" nodes of the graphdomenica 23 ottobre 11
  44. 44. Given your dataset, organize some clusters Are there some nodes which cannot belong to a cluster? They probably have some properties different from the averagedomenica 23 ottobre 11
  45. 45. Given your dataset, organize some clusters Are there some nodes which cannot belong to a cluster? They probably have some properties different from the average ACHTUNG! TERRORISTEN!domenica 23 ottobre 11
  46. 46. but ... why graphDB? 38domenica 23 ottobre 11
  47. 47. Representing a Graph in: http://www.slideshare.net/slidarko/problemsolving-using-graph-traversals-searching- scoring-ranking-and-recommendation# 39domenica 23 ottobre 11
  48. 48. Representing a Graph in: http://www.slideshare.net/slidarko/problemsolving-using-graph-traversals-searching- scoring-ranking-and-recommendation#✓ Relational Database (mysql, oracle)✓ Document Oriented DB (mongodb, couchdb)✓ XML Database (MarkLogic, eXist-db) 39domenica 23 ottobre 11
  49. 49. where is the difference ? 40domenica 23 ottobre 11
  50. 50. GraphDB A graph database is any storage system that provides index-free adjacency. http://www.slideshare.net/slidarko/problemsolving-using-graph-traversals-searching-scoring-ranking-and-recommendationdomenica 23 ottobre 11
  51. 51. Step by step example Given a list of people, find their homepages 42domenica 23 ottobre 11
  52. 52. Tree-based DB WAY 1 43domenica 23 ottobre 11
  53. 53. Tree-based DB WAY David Funaro put in the Search Engine 2 1 43domenica 23 ottobre 11
  54. 54. Tree-based DB WAY David Funaro put in the Search Engine 2 find 3 1 http://davidfunaro.com 43domenica 23 ottobre 11
  55. 55. Tree-based DB WAY David Funaro The cost to find Search Engine friend HP put in the a single 2 grows as the friends HP tables grows find 3 1 http://davidfunaro.com 43domenica 23 ottobre 11
  56. 56. GraphDB WAY it’s like that the GraphDB has an additional information (the ancor <a>) 44domenica 23 ottobre 11
  57. 57. GraphDB WAY 1 get the embedded information(index) www.odino.org it’s like that the GraphDB has an additional information (the ancor <a>) 44domenica 23 ottobre 11
  58. 58. GraphDB WAY The Anchor work as a local index to reach the document = index-free adjacency <a href=”http://odino.org”> Alessandro Nadalin </a> 45domenica 23 ottobre 11
  59. 59. Local cost The local cost is O(k) = Constant 46domenica 23 ottobre 11
  60. 60. Local cost The local cost is O(k) = Constant 47domenica 23 ottobre 11
  61. 61. Local cost 48domenica 23 ottobre 11
  62. 62. Local cost Thus, as the graph grows in size, the cost of a local step remain the same 48domenica 23 ottobre 11
  63. 63. any database can implicity represent a graph BUT only a graph database make the graph structure explicit 49domenica 23 ottobre 11
  64. 64. Benchmark Deph RDBMS Graph 1 100ms 30ms • 1 Million Vertex • 4 Million Edge 2 1000ms 500ms • Scale Free Tolopogy 3 10000ms 3000ms • Postgres VS Neo4J 4 100000m 50000ms s • Both Hash and BTree 5 N/A 100000m s 50 http://markorodriguez.com/2011/02/18/mysql-vs-neo4j-on-a-large-scale-graph-traversal/domenica 23 ottobre 11
  65. 65. community that is building and feeding the GraphDB ecosystem GraphDB community ThinkerPop Stack Databasesdomenica 23 ottobre 11
  66. 66. data model and their implementation Blueprints is a collection of interfaces, implementations, ouplementations, and test suites for the property graph data model. Blueprints is analogous to the JDBC, but for graph databases. https://github.com/tinkerpop/blueprints/wiki/domenica 23 ottobre 11
  67. 67. a data flow Framework using Process Graph provide a collection of "pipes" that are connected togheter to from processing pipelinesdomenica 23 ottobre 11
  68. 68. a graph-based programming language. a Turing-Complete graph-base programming language that compiles Gremlin syntax down to Pipesdomenica 23 ottobre 11
  69. 69. a REST-full graph shell. Allow blueprints graph to be exposed through a RESTful API (HTTP)domenica 23 ottobre 11
  70. 70. Whats hotdomenica 23 ottobre 11
  71. 71. OrientDBdomenica 23 ottobre 11
  72. 72. Glossary RID <10:05> Cluster Position 58domenica 23 ottobre 11
  73. 73. Glossary RID <10:05> Cluster Position CLASS 58domenica 23 ottobre 11
  74. 74. Main featuresdomenica 23 ottobre 11
  75. 75. Inheritancedomenica 23 ottobre 11
  76. 76. class Vehicle class Car class Bikedomenica 23 ottobre 11
  77. 77. class Vehicle class Car class Bike SELECT FROM Vehicle WHERE owner = 1:1domenica 23 ottobre 11
  78. 78. class Vehicle class Car class Bike can return records of class Bike or Cardomenica 23 ottobre 11
  79. 79. Traversaldomenica 23 ottobre 11
  80. 80. domenica 23 ottobre 11
  81. 81. SELECT FROM fellas WHERE any() traverse(0,-1) ( @rid = [Michelle @rid] ) 66domenica 23 ottobre 11
  82. 82. SELECT FROM fellas WHERE any() traverse(0,-1) ( @rid = [Michelle @rid] ) 67domenica 23 ottobre 11
  83. 83. SELECT FROM fellas WHERE any() traverse(0,2) ( @rid = = [Michelle @rid] )) SELECT FROM fellas WHERE any() traverse(0,2) ( @rid [Michelle @rid]domenica 23 ottobre 11
  84. 84. SELECT FROM fellas WHERE any() traverse(0,2) ( @rid = [Michelle @rid] )domenica 23 ottobre 11
  85. 85. SQL synthaxdomenica 23 ottobre 11
  86. 86. beyond SQLdomenica 23 ottobre 11
  87. 87. SELECT FROM authors WHERE book.title = ...domenica 23 ottobre 11
  88. 88. domenica 23 ottobre 11 ACID
  89. 89. speaks JSONdomenica 23 ottobre 11
  90. 90. { "schema": { "name": "Address" }, "result": [{ "@type": "d", "@rid": "#13:0", "@version": 6, "@class": "Address", "type": "Residence", "street": "Piazza Navona, 1", "city": "#14:0", "nick": "Luca2" }, { ... ...domenica 23 ottobre 11
  91. 91. Double Protocoldomenica 23 ottobre 11
  92. 92. HTTPdomenica 23 ottobre 11
  93. 93. HTTP Universaldomenica 23 ottobre 11
  94. 94. HTTPEasy to interact withdomenica 23 ottobre 11
  95. 95. binarydomenica 23 ottobre 11
  96. 96. binary Blazing fastdomenica 23 ottobre 11
  97. 97. on-record SELECTsdomenica 23 ottobre 11
  98. 98. SELECT FROM catsdomenica 23 ottobre 11
  99. 99. SELECT FROM catsdomenica 23 ottobre 11
  100. 100. SELECT FROM 11:0domenica 23 ottobre 11
  101. 101. SELECT FROM 11:0domenica 23 ottobre 11
  102. 102. SELECT FROM [11:0,11:1]domenica 23 ottobre 11
  103. 103. SELECT FROM [11:0,11:1]domenica 23 ottobre 11
  104. 104. SELECT FROM [11:0,12:0]domenica 23 ottobre 11
  105. 105. SELECT FROM [11:0,12:0]domenica 23 ottobre 11
  106. 106. stress-free setupdomenica 23 ottobre 11
  107. 107. 2 Mbdomenica 23 ottobre 11
  108. 108. ./orient/bin/server.sh 93domenica 23 ottobre 11
  109. 109. in-memory DBdomenica 23 ottobre 11
  110. 110. or disk-persisteddomenica 23 ottobre 11
  111. 111. Supports standards Supports standards 96 domenica 23 ottobre 11
  112. 112. •Inheritance •Traversal •Sql syntax like •ACID OrientDB •Speak JSON •Double protocol •on-record Select •ThinkerPop Compliantdomenica 23 ottobre 11
  113. 113. Oh, its Java. 98domenica 23 ottobre 11
  114. 114. PHP ?domenica 23 ottobre 11
  115. 115. somebody started writing the binary-protocol binding https://github.com/AntonTerekhov/OrientDB-PHP ( beta0.4.1, 28 April 2010 )domenica 23 ottobre 11
  116. 116. $db = new OrientDB($host, $port); $record = $db->recordLoad(1:1, *:-1); // $record instance of OrientDBRecorddomenica 23 ottobre 11
  117. 117. and othersdomenica 23 ottobre 11
  118. 118. domenica 23 ottobre 11
  119. 119. Orient Library ... are writing a complete library https://github.com/congow/Orient 104domenica 23 ottobre 11
  120. 120. Orient = PHP Library to work with OrientDB 105domenica 23 ottobre 11
  121. 121. Data Mapper Query Builder HTTP Bindingdomenica 23 ottobre 11
  122. 122. HTTP Bindingdomenica 23 ottobre 11
  123. 123. use CongowOrient;use CongowOrientFoundationBinding;$driver   = new OrientHttpClientCurl();$orient   = new Binding($driver, 127.0.0.1, 2480, admin, admin, demo);$response = $orient->query("SELECT FROM Address");$output   = json_decode($response->getBody());foreach ($output->result as $address){  var_dump($address->street);}domenica 23 ottobre 11
  124. 124. use CongowOrient;use CongowOrientFoundationBinding;$driver   = new OrientHttpClientCurl();$orient   = new Binding($driver, 127.0.0.1, 2480, admin, admin, demo);$response = $orient->query("SELECT FROM Address");$output   = json_decode($response->getBody());foreach ($output->result as $address){  var_dump($address->street);}domenica 23 ottobre 11
  125. 125. use CongowOrient;use CongowOrientFoundationBinding;$driver   = new OrientHttpClientCurl();$orient   = new Binding($driver, 127.0.0.1, 2480, admin, admin, demo);$response = $orient->query("SELECT FROM Address");$output   = json_decode($response->getBody());foreach ($output->result as $address){  var_dump($address->street);}domenica 23 ottobre 11
  126. 126. use CongowOrient;use CongowOrientFoundationBinding;$driver   = new OrientHttpClientCurl();$orient   = new Binding($driver, 127.0.0.1, 2480, admin, admin, demo);$response = $orient->query("SELECT FROM Address");$output   = json_decode($response->getBody());foreach ($output->result as $address){  var_dump($address->street);}domenica 23 ottobre 11
  127. 127. use CongowOrient;use CongowOrientFoundationBinding;$driver   = new OrientHttpClientCurl();$orient   = new Binding($driver, 127.0.0.1, 2480, admin, admin, demo);$response = $orient->query("SELECT FROM Address");$output   = json_decode($response->getBody());foreach ($output->result as $address){  var_dump($address->street); { "schema": { "name": "Address"} }, "result": [{ "@type": "d", "@rid": "#13:0", "@version": 6, "@class": "Address", "type": "Residence", "street": "Piazza Navona, 1", "city": "#14:0", "nick": "Luca2" }, { ... ...domenica 23 ottobre 11
  128. 128. apart from ->query($SQL)domenica 23 ottobre 11
  129. 129. ->get|delete|postClass($class)domenica 23 ottobre 11
  130. 130. ->post|delete|put|getDocument($rid)domenica 23 ottobre 11
  131. 131. ...and much more! (connect, disconnect, ...)domenica 23 ottobre 11
  132. 132. Query Builderdomenica 23 ottobre 11
  133. 133. use CongowOrientQuery;$query = new Query();$query->from(array(users))->where(username = ?, "admin");echo $query->getRaw(); // SELECT FROM users WHERE username = "admin"domenica 23 ottobre 11
  134. 134. use CongowOrientQuery;$query = new Query();$query->from(array(users))->where(username = ?, "admin");echo $query->getRaw(); // SELECT FROM users WHERE username = "admin"domenica 23 ottobre 11
  135. 135. use CongowOrientQuery;$query = new Query();$query->from(array(users))->where(username = ?, "admin");echo $query->getRaw(); // SELECT FROM users WHERE username = "admin"domenica 23 ottobre 11
  136. 136. use CongowOrientQuery;$query = new Query();$query->from(array(users))->where(username = ?, "admin");echo $query->getRaw(); // SELECT FROM users WHERE username = "admin"domenica 23 ottobre 11
  137. 137.    $query->select(array(name, username, email), false)    ->from(array(12:0, 12:1), false)    ->where(any() traverse ( any() like "%danger%" ))    ->orWhere("1 = ?", 1)    ->andWhere("links = ?", 1)    ->limit(20)    ->orderBy(username)    ->orderBy(name, true, true)    ->range("12:0", "12:1");  SELECT name, username, email   FROM [12:0, 12:1]   WHERE any() traverse ( any() like "%danger%" )  OR 1 = "1" AND links = "1"   ORDER BY name, username   LIMIT 20   RANGE 12:0 12:1 domenica 23 ottobre 11
  138. 138. Data Mapperdomenica 23 ottobre 11
  139. 139. A Doctrine2 strange ODMdomenica 23 ottobre 11
  140. 140. namespace PolandPHPConEntity;use CongowOrientODMMapperAnnotations as ODM;/*** @ODMDocument(class="Person")*/class Speaker{    /**     * @ODMProperty( type="string")     */    protected $name;    public function setName($name)    {        $this->name = $name;    }domenica 23 ottobre 11
  141. 141. namespace PolandPHPConEntity;use CongowOrientODMMapperAnnotations as ODM;/*** @ODMDocument(class="Person")*/class Speaker{    /**     * @ODMProperty(type="string")     */    protected $name;    public function setName($name)    {        $this->name = $name;    }domenica 23 ottobre 11
  142. 142. namespace PolandPHPConEntity;use CongowOrientODMMapperAnnotations as ODM;/*** @ODMDocument(class="Person")*/class Speaker{    /**     * @ODMProperty(type="string")     */    protected $name;    public function setName($name)    {        $this->name = $name;    }domenica 23 ottobre 11
  143. 143. namespace PolandPHPConEntity;use CongowOrientODMMapperAnnotations as ODM;/*** @ODMDocument(class="Person")*/class Speaker{    /**     * @ODMProperty(type="string")     */    protected $name;    public function setName($name)    {        $this->name = $name;    }domenica 23 ottobre 11
  144. 144. Domain Driven Designdomenica 23 ottobre 11
  145. 145. { "schema": { "name": "Speaker" }, "result": [{ "@type": "d", "@rid": "#1:0", "@version": 6, "@class": "Speaker", "name": "David Coallier" }, { ... ...domenica 23 ottobre 11
  146. 146. { "schema": { "name": "Speaker" }, "result": [{ "@type": "d", "@rid": "#1:0", "@version": 6, "@class": "Speaker", "name": "David Coallier" }, { ... ... $david = $mapper->hydrate(json_decode($speaker));domenica 23 ottobre 11
  147. 147. { "schema": { "name": "Speaker" }, "result": [{ "@type": "d", "@rid": "#1:0", "@version": 6, "@class": "Speaker", "name": "David Coallier" }, { ... ... $david instanceOf PolandPHPConEntitySpeakerdomenica 23 ottobre 11
  148. 148. Repository Pattern $repo = $manager->getRepository(Speaker)domenica 23 ottobre 11
  149. 149. $speakers = $repo->findAll();domenica 23 ottobre 11
  150. 150. $speaker = $repo->find($rid);domenica 23 ottobre 11
  151. 151. $criteria = array(Name => Lorna); $lornas = $repo->findBy($criteria);domenica 23 ottobre 11
  152. 152. $criteria = array( Name => Lorna, last_name => Jane ); $lornaJ = $repo->findOneBy($criteria);domenica 23 ottobre 11
  153. 153. Know your boundaries 138domenica 23 ottobre 11
  154. 154. https://github.com/doctrine/common/tree/master/lib/Doctrine/Common/Persistence 139domenica 23 ottobre 11
  155. 155. Theory sucks. 140domenica 23 ottobre 11
  156. 156. Demodomenica 23 ottobre 11
  157. 157. Demo Menu items in RDBMS id type page url 1 external NULL http://www.google.com 2 page 1 NULL 142domenica 23 ottobre 11
  158. 158. Demo Menu items in OrientDB Link PageLink rid title {page ExternalLink rid title url 9:1 home 1 8:2 google google.com 143domenica 23 ottobre 11
  159. 159. That’s all, folks! 144domenica 23 ottobre 11
  160. 160. That’s all, folks! David Funaro @ingdavidino http://davidfunaro.com 144domenica 23 ottobre 11
  161. 161. That’s all, folks! David Funaro Alessandro Nadalin @ingdavidino @_odino_ http://davidfunaro.com http://odino.org 144domenica 23 ottobre 11
  162. 162. That’s all, folks! David Funaro Alessandro Nadalin @ingdavidino @_odino_ http://davidfunaro.com http://odino.org 144domenica 23 ottobre 11
  163. 163. Credits http://www.flickr.com/photos/sayamindu/5677281218/sizes/l/in/photostream/ http://farm1.static.flickr.com/182/471383865_79d04aec36_o.png http://farm1.static.flickr.com/134/318947873_12028f1b66_b.jpg http://www.flickr.com/photos/atomdocs/3275758118/sizes/o/in/photostream/ http://www.flickr.com/photos/pattipics/5229478393/sizes/o/in/photostream/ http://www.flickr.com/photos/kongharald/366597251/sizes/o/in/photostream/ http://www.everaldo.com/ http://www.flickr.com/photos/tusnelda/6140792529/sizes/l/in/photostream/ http://www.flickr.com/photos/mondi/5368644355/sizes/l/in/photostream/ http://www.flickr.com/photos/jayneandd/4191106566/sizes/l/in/photostream/ http://www.flickr.com/photos/jooon/2093253534/sizes/l/in/photostream/ http://www.flickr.com/photos/bluedharma/89186151/sizes/o/in/photostream/ http://www.flickr.com/photos/exfordy/2747089295/sizes/l/in/photostream/ http://www.flickr.com/photos/nostri-imago/3137422976/sizes/o/in/photostream/ http://www.flickr.com/photos/fionasjournal/379587818/sizes/z/in/photostream/ http://www.flickr.com/photos/nperlapro/1297392267/ http://www.flickr.com/photos/fastphive/28428808/sizes/m/in/photostream/ http://www.flickr.com/photos/rnugraha/2003147365/sizes/o/in/photostream/ http://www.flickr.com/photos/zigazou76/4412946911/sizes/l/in/photostream/ http://www.flickr.com/photos/greatnet/4667555436/sizes/l/in/photostream/ http://www.flickr.com/photos/mnsc/2768391365/sizes/l/in/photostream/ http://www.flickr.com/photos/christmaswithak/4675962453/sizes/l/in/photostream/ http://www.amazon.com/Trainspotting-Irvine-Welsh/dp/0393314804 http://www.flickr.com/photos/franconadalin59/5778176872/sizes/l/in/photostream/ http://farm6.static.flickr.com/5176/5474445627_875d621689_b.jpg http://farm3.static.flickr.com/2243/2189435082_a16d3c89ae_b.jpg http://farm3.static.flickr.com/2647/3816311930_ac52cff491_o.jpg http://i130.photobucket.com/albums/p266/feike1977/PES6-4-3-3defencesettings.jpg http://images.usatoday.com/life/_photos/2006/11/30/numb3rs-topper.jpg http://www.flickr.com/photos/jakecaptive/3205277810/sizes/l/in/photostream/domenica 23 ottobre 11

×