SPL, not a bridge too far
 phpNW 2009 - Manchester
Who am I ?
Michelangelo van Dam
Independent Enterprise PHP consultant
Co-founder PHPBenelux UG


@dragonbe

http://dragonb...
Who are you ?

    Are you familiar with php.net ?
    Are you familiar with arrays ?
     Are you familiar with OOP ?
Are...
What is SPL ?
              Standard PHP Library
          interfaces, classes and methods
      solve common development ...
SPL by Marcus Börger



               Ittteerrrattor




         5
Definition of SPL


SPL provides a huge toolkit that assists you to easily
iterate over a diversity of data structures in a...
What does it provide ?
•   ArrayObject - approach arrays as objects
•   Iterators - various iterators
•   Interfaces - ite...
<?php phpinfo(); ?>




         8
ArrayObject

•   provides an interface
    - treat arrays as objects
    - elements are iteratable
    - provides serializ...
<?php
        ArrayObject Example
$myArray = array (
    'time'      => 'Derick Rethans',
    'test'      => 'Sebastian Be...
ArrayObject output
ArrayObject Object
(
    [storage:ArrayObject:private] => Array
        (
            [time] => Derick ...
…
       More of ArrayObject
// serializing object for caching, sessions, …
$obj->serialize();

// adding more key/value e...
Iterator
•   provides a common interface
•   to iterate over “things”
    - xml data
    - database data
    - arrays
•   ...
Advantage ?

•   Reusable code
    - data structures can change
    - object oriented
      ✓ extending
      ✓ refactorin...
Example


•   retrieve data in an array
•   with items filtered out




                                15
<?php
        FilterIterator Example
class filterOut extends FilterIterator
{
    private $_filter;

    public function _...
<?php
        FilterIterator output
$myArray = array (
    'time'      => 'Derick Rethans',
    'test'      => 'Sebastian ...
DirectoryIterator


•   dealing with files and directories
•   looping back and forth between files
•   sorting files w/ or w...
Example


•   show an list of archive files
•   ordered with last archive first




                             19
Directory contents
$ ls archives/
datafile-20090901.csv   datafile-20090906.csv
datafile-20090911.csv   datafile-20090916....
SortableDirectorIterator
<?php
class SortableDirectoryIterator extends DirectoryIterator
{
    public function sortUp()
  ...
…
    Our Storage Container
    protected function _getStorage()
    {
        $obj = new ArrayObject();
        foreach (...
Voila ! Descending filenames
./archives/datafile-20090930.csv        ./archives/datafile-20090915.csv
./archives/datafile-2...
RecursiveIteratorIterator

•   iterates over existing iterator
•   over multiple levels
•   easy to flatten out nested arra...
Example
                              Chuck Norris
                             Account Manager



            Jane Doe   ...
RecursiveIteratorIterator
<?php
$company = array (
    array (
        'name' => 'Chuck Norris','position' => 'Account Man...
Flattened Array output
$iterator = new RecursiveArrayIterator(new ArrayObject($company));
$ritit = new RecursiveIteratorIt...
Interfaces

•   Countable: an internal counter
•   OuterIterator: iteration over inner iterators
•   RecursiveIterator: it...
<?php
              Interface example
// file: Order.php

class Order implements Countable, SplSubject
{
    protected $_o...
Interface Example (2)
    public function notify()
    {
        foreach ($this->_orders as $obj) {
            $obj->upda...
Running Interface Example
<?php

require_once 'Order.php';
require_once 'PlaceOrder.php';

$order = new Order();
$placeOrd...
SPL Exceptions
•   SPL Exceptions
    - templates
    - throw exceptions
    - common issues
•   Types of exceptions
    -...
SPL LogicException Tree




           33
SPL RuntimeException Tree




            34
<?php
           Exceptions Example
//file: spl_exception01.php
class MyClass
{
    public function giveANumberFromOneToTe...
SplFunctions


•   functions for PHP and SPL in particular
•   often dealing with auto loading
•   some for internal refer...
<?php
        SplFunctions Example
interface foo {}
interface bar {}

class baz implements foo, bar {}
class example exten...
Output of SplFunctions
array(2) {
  ["foo"]=>
  string(3)   "foo"
  ["bar"]=>
  string(3)   "bar"
}
array(2) {
  ["bar"]=>...
SPLFileInfo


The SplFileInfo class offers a high-level object oriented
    interface to information for an individual file...
<?php
         SplFileInfo Example
// use the current file to get information from
$file = new SplFileInfo(dirname(__FILE_...
Processing CSV with SPL
Consider the following data.csv


Derick Rethans;time
Sebastian Bergmann;test
Marcus Börger;iterat...
<?php
          SPL usage on CSV
$info = new SplFileInfo('data.csv');
if ($info->isReadable()) {
    $file = $info->openFi...
Data Structures
•   Available in PHP 5.3.x

    -   SplDoublyLinkedList
        ✓ SplStack
        ✓ SplQueue
        ✓ Sp...
Data Structures Example
<?php
// file: spl_stack01.php
$stack = new SplStack();
$stack->push('Message 1');
$stack->push('M...
SplHeap

•   SplHeap is an abstract class
    - SplMinHeap implements SplHeap (low » high)
    - SplMaxHeap implements Spl...
Simple SplHeap example
<?php

$heap = new SplMinHeap;
$heap->insert(5);
$heap->insert(2);
$heap->insert(8);
$heap->insert(...
JupilerLeague with SplHeap
<?php
class JupilerLeague extends SplHeap
{
    public function compare($array1, $array2)
    {...
JupilerLeague Scoreboard
Club Brugge: 21
Anderlecht: 20
Standard: 20
KV Mechelen: 18
STVV: 17
Zulte Waregem: 15
AA Gent: 1...
Conclusion


SPL can help you solve common PHP issues
        it’s built-in, so why not use it
   it requires no “advanced...
SPL is not all good

•   Matthew “Elazar” Turland pointed out:
    - Performance could be better (SPLStack)
    - ArrayObj...
Not a bridge too far...




           51
Only one minor thing...




           52
Documentation problem

•   php.net/spl needs more documentation !
•   you can help on that part:
    - see http://elizabet...
More about SPL...
•   main SPL documentation:
    http://php.net/spl
•   PHPro Tutorials on SPL:
    http://www.phpro.org/...
License
This presentation is released under the Creative Commons
Attribution-Share Alike 3.0 Unported License
You are free...
Photo credits

       Marcus Boerger - Sebastian Bergmann
http://www.flickr.com/photos/sebastian_bergmann/173891817

      ...
Thank you

        Find my slides on
http://slideshare.net/DragonBe

    Don’t forget to vote !
     http://joind.in/613

...
Upcoming SlideShare
Loading in …5
×

Spl Not A Bridge Too Far phpNW09

5,973 views
5,902 views

Published on

SPL provides a lot of classes and interfaces to make your code more readable, maintainable and testable.

Published in: Technology, Education
2 Comments
7 Likes
Statistics
Notes
No Downloads
Views
Total views
5,973
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
157
Comments
2
Likes
7
Embeds 0
No embeds

No notes for slide
  • Promotion of SPL
  • now everyone uses PHP 5.3 for new projects, right ?
  • Created by Marcus Boerger
  • Created by Marcus Boerger
  • Definition of SPL
  • SPL functions
    - base class functions
    - autoloading functions
  • - looking at phpinfo() for PHP 5.3
    - 6 interfaces
    - 39 classes
  • easiest way converting array into an object
  • blue code is a lambda or anonymous function used in PHP 5.3
  • More info found on http://php.net/spl
  • arrays are stored as a whole in memory
    iterators just the element that has focus
  • interface: design by contract for classes
  • example using SplObserver, SplSubject and Countable interface
  • definition from php.net
  • SplInfo provides a lot of details about a given file
  • work with lots of CSV data files
  • using a data model, makes things a whole lot easier
  • let&amp;#x2019;s start of simple, using the SplStack here
  • simple example processing test results
  • modify the compare method to use the values of a given array
    Soccer != football, but you get the point here, right ?
  • so, once you know how, it&amp;#x2019;s not a bridge too far
  • Spl Not A Bridge Too Far phpNW09

    1. 1. SPL, not a bridge too far phpNW 2009 - Manchester
    2. 2. Who am I ? Michelangelo van Dam Independent Enterprise PHP consultant Co-founder PHPBenelux UG @dragonbe http://dragonbe.com 2
    3. 3. Who are you ? Are you familiar with php.net ? Are you familiar with arrays ? Are you familiar with OOP ? Are you familiar with Design Patterns ? 3
    4. 4. What is SPL ? Standard PHP Library interfaces, classes and methods solve common development challenges Available since PHP 5.0 !!! As of 5.3 SPL cannot be turned off from the source ! 4
    5. 5. SPL by Marcus Börger Ittteerrrattor 5
    6. 6. Definition of SPL SPL provides a huge toolkit that assists you to easily iterate over a diversity of data structures in a standardized way 6
    7. 7. What does it provide ? • ArrayObject - approach arrays as objects • Iterators - various iterators • Interfaces - iterator interfaces for your objects • Exceptions - exceptions to streamline error handling • SPL Functions - extra functions and autoloader func • SplFileInfo - tools for filesystem access • Data structures - structuring data sequences 7
    8. 8. <?php phpinfo(); ?> 8
    9. 9. ArrayObject • provides an interface - treat arrays as objects - elements are iteratable - provides serializing and deserializing of arrays - sorting elements (w/ or w/o callback methods) - exchange elements with other arrays or objects 9
    10. 10. <?php ArrayObject Example $myArray = array ( 'time' => 'Derick Rethans', 'test' => 'Sebastian Bergmann', 'iterate' => 'Marcus Börger', ); $obj = new ArrayObject($myArray); print_r($obj); $obj->uasort(function ($a, $b) { if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; }); print_r($obj); 10
    11. 11. ArrayObject output ArrayObject Object ( [storage:ArrayObject:private] => Array ( [time] => Derick Rethans [test] => Sebastian Bergmann [iterate] => Marcus Börger ) ) ArrayObject Object ( [storage:ArrayObject:private] => Array ( [time] => Derick Rethans [iterate] => Marcus Börger [test] => Sebastian Bergmann ) ) 11
    12. 12. … More of ArrayObject // serializing object for caching, sessions, … $obj->serialize(); // adding more key/value elements to the stack $obj->offsetSet('enterprise', 'Ivo Jansch'); // removing by key $obj->offsetUnset('time'); … 12
    13. 13. Iterator • provides a common interface • to iterate over “things” - xml data - database data - arrays • move back and forth in a stack • distinct methods to access keys and values • specific iterators for different purposes 13
    14. 14. Advantage ? • Reusable code - data structures can change - object oriented ✓ extending ✓ refactoring ✓ overloading 14
    15. 15. Example • retrieve data in an array • with items filtered out 15
    16. 16. <?php FilterIterator Example class filterOut extends FilterIterator { private $_filter; public function __construct(Iterator $it, $filter) { parent::__construct($it); $this->_filter = $filter; } public function accept() { $key = $this->getInnerIterator()->key(); return ($key == $this->_filter) ? false : true; } } 16
    17. 17. <?php FilterIterator output $myArray = array ( 'time' => 'Derick Rethans', 'test' => 'Sebastian Bergmann', 'iterate' => 'Marcus Börger', ); $obj = new ArrayObject($myArray); $iterator = new filterOut($obj->getIterator(), 'time'); foreach ($iterator as $item) { var_dump($item); } //ouputs string(18) "Sebastian Bergmann" string(13) "Marcus Börger" 17
    18. 18. DirectoryIterator • dealing with files and directories • looping back and forth between files • sorting files w/ or w/o custom sorting algorithms 18
    19. 19. Example • show an list of archive files • ordered with last archive first 19
    20. 20. Directory contents $ ls archives/ datafile-20090901.csv datafile-20090906.csv datafile-20090911.csv datafile-20090916.csv datafile-20090921.csv datafile-20090926.csv datafile-20090902.csv datafile-20090907.csv datafile-20090912.csv datafile-20090917.csv datafile-20090922.csv datafile-20090927.csv datafile-20090903.csv datafile-20090908.csv datafile-20090913.csv datafile-20090918.csv datafile-20090923.csv datafile-20090928.csv datafile-20090904.csv datafile-20090909.csv datafile-20090914.csv datafile-20090919.csv datafile-20090924.csv datafile-20090929.csv datafile-20090905.csv datafile-20090910.csv datafile-20090915.csv datafile-20090920.csv datafile-20090925.csv datafile-20090930.csv 20
    21. 21. SortableDirectorIterator <?php class SortableDirectoryIterator extends DirectoryIterator { public function sortUp() { $storage = $this->_getStorage(); $storage->uksort(function ($a, $b) { if ($a == $b) return 0; return ($a < $b) ? -1 : 1; }); return $storage; } public function sortDown() { $storage = $this->_getStorage(); $storage->uksort(function ($a, $b) { if ($a == $b) return 0; return ($a < $b) ? 1 : -1; }); return $storage; } 21
    22. 22. … Our Storage Container protected function _getStorage() { $obj = new ArrayObject(); foreach ($this as $file) { if ($file->isDot()) continue; $obj->offsetSet($file->getFileName(), $file->getFileInfo()); } return $obj; } } $dir = new SortableDirectoryIterator('./archives'); $sortObj = $dir->sortDown(); $iterator = $sortObj->getIterator(); while ($iterator->valid()) { echo $iterator->current()->getPathName() . PHP_EOL; $iterator->next(); } 22
    23. 23. Voila ! Descending filenames ./archives/datafile-20090930.csv ./archives/datafile-20090915.csv ./archives/datafile-20090929.csv ./archives/datafile-20090914.csv ./archives/datafile-20090928.csv ./archives/datafile-20090913.csv ./archives/datafile-20090927.csv ./archives/datafile-20090912.csv ./archives/datafile-20090926.csv ./archives/datafile-20090911.csv ./archives/datafile-20090925.csv ./archives/datafile-20090910.csv ./archives/datafile-20090924.csv ./archives/datafile-20090909.csv ./archives/datafile-20090923.csv ./archives/datafile-20090908.csv ./archives/datafile-20090922.csv ./archives/datafile-20090907.csv ./archives/datafile-20090921.csv ./archives/datafile-20090906.csv ./archives/datafile-20090920.csv ./archives/datafile-20090905.csv ./archives/datafile-20090919.csv ./archives/datafile-20090904.csv ./archives/datafile-20090918.csv ./archives/datafile-20090903.csv ./archives/datafile-20090917.csv ./archives/datafile-20090902.csv ./archives/datafile-20090916.csv ./archives/datafile-20090901.csv 23
    24. 24. RecursiveIteratorIterator • iterates over existing iterator • over multiple levels • easy to flatten out nested array structures • controlling recursive interactions 24
    25. 25. Example Chuck Norris Account Manager Jane Doe John Doe Project Manager Project Manager Cinderella Shrek Developer Graphical Designer 25
    26. 26. RecursiveIteratorIterator <?php $company = array ( array ( 'name' => 'Chuck Norris','position' => 'Account Manager', 'manages' => array ( array ( 'name' => 'Jane Doe','position' => 'Project Manager', 'manages' => array ( array ( 'name' => 'Cinderella','position' => 'Developer', 'manages' => array (), ), array ( 'name' => 'Shrek','position' => 'Graphical Designer', 'manages' => array (), ), ), ), array ( 'name' => 'John Doe','position' => 'Project Manager', 'manages' => array (), ), ), ), ); 26
    27. 27. Flattened Array output $iterator = new RecursiveArrayIterator(new ArrayObject($company)); $ritit = new RecursiveIteratorIterator($iterator); foreach ($ritit as $key => $value) { echo $key . ' = ' . $value . PHP_EOL; } // outputs name = Chuck Norris position = Account Manager name = Jane Doe position = Project Manager name = Cinderella position = Developer name = Shrek position = Graphical Designer name = John Doe position = Project Manager 27
    28. 28. Interfaces • Countable: an internal counter • OuterIterator: iteration over inner iterators • RecursiveIterator: iterating in an recursive way • SeekableIterator: an internal stack seeker • SplObserver: implements observer pattern • SplSubject: implements observer pattern 28
    29. 29. <?php Interface example // file: Order.php class Order implements Countable, SplSubject { protected $_orders; protected $_count; public function __construct() { $this->_count = 0; $this->_orders = array (); } public function placeOrder() { $this->_count++; } public function attach(SplObserver $observer) { $this->_orders[] = $observer; } public function detach(SplObserver $observer) { // not used in this case } 29
    30. 30. Interface Example (2) public function notify() { foreach ($this->_orders as $obj) { $obj->update($this); } } public function count() { return $this->_count; } } <?php // file: PlaceOrder.php class PlaceOrder implements SplObserver { public function update(SplSubject $order) { echo 'We have ' . count($order) . ' orders now' . PHP_EOL; } } 30
    31. 31. Running Interface Example <?php require_once 'Order.php'; require_once 'PlaceOrder.php'; $order = new Order(); $placeOrder = new PlaceOrder(); $order->attach($placeOrder); $order->notify(); $order->placeOrder(); $order->notify(); $order->placeOrder(); $order->notify(); $ php ./spl_observer.php We have 0 orders now We have 1 orders now We have 2 orders now 31
    32. 32. SPL Exceptions • SPL Exceptions - templates - throw exceptions - common issues • Types of exceptions - LogicExceptions - RuntimeExceptions 32
    33. 33. SPL LogicException Tree 33
    34. 34. SPL RuntimeException Tree 34
    35. 35. <?php Exceptions Example //file: spl_exception01.php class MyClass { public function giveANumberFromOneToTen($number) { if($number < 1 || $number > 10) { throw new OutOfBoundsException('Number should be between 1 and 10'); } echo $number . PHP_EOL; } } $my = new MyClass(); try { $my->giveANumberFromOneToTen(5); $my->giveANumberFromOneToTen(20); } catch (OutOfBoundsException $e) { echo $e->getMessage() . PHP_EOL; } Output: $ /usr/bin/php ./spl_exception01.php 5 Number should be between 1 and 10 35
    36. 36. SplFunctions • functions for PHP and SPL in particular • often dealing with auto loading • some for internal referencing 36
    37. 37. <?php SplFunctions Example interface foo {} interface bar {} class baz implements foo, bar {} class example extends baz {} var_dump(class_implements(new baz)); var_dump(class_implements(new example)); 37
    38. 38. Output of SplFunctions array(2) { ["foo"]=> string(3) "foo" ["bar"]=> string(3) "bar" } array(2) { ["bar"]=> string(3) "bar" ["foo"]=> string(3) "foo" } 38
    39. 39. SPLFileInfo The SplFileInfo class offers a high-level object oriented interface to information for an individual file. 39
    40. 40. <?php SplFileInfo Example // use the current file to get information from $file = new SplFileInfo(dirname(__FILE__)); var_dump($file->isFile()); var_dump($file->getMTime()); var_dump($file->getSize()); var_dump($file->getFileInfo()); var_dump($file->getOwner()); //output bool(false) int(1244760945) int(408) object(SplFileInfo)#2 (0) { } int(501) 40
    41. 41. Processing CSV with SPL Consider the following data.csv Derick Rethans;time Sebastian Bergmann;test Marcus Börger;iterate Ivo Jansch;enterprise Matthew Weier O'Phinney;extend Michelangelo van Dam;elephpant 41
    42. 42. <?php SPL usage on CSV $info = new SplFileInfo('data.csv'); if ($info->isReadable()) { $file = $info->openFile(); $file->setFlags(SplFileObject::READ_CSV); $file->setCsvControl(';','"'); foreach ($file as $row) { list ($user, $term) = $row; if (null !== $user && null !== $term) { echo $user . ' is known for ' . $term . PHP_EOL; } } } //outputs Derick Rethans is known for time Sebastian Bergmann is known for test Marcus Börger is known for iterate Ivo Jansch is known for enterprise Matthew Weier O'Phinney is known for extend Michelangelo van Dam is known for elephpant 42
    43. 43. Data Structures • Available in PHP 5.3.x - SplDoublyLinkedList ✓ SplStack ✓ SplQueue ✓ SplHeap ✓ SplMaxHeap ✓ SplMinHeap ✓ SplPriorityQueue 43
    44. 44. Data Structures Example <?php // file: spl_stack01.php $stack = new SplStack(); $stack->push('Message 1'); $stack->push('Message 2'); $stack->push('Message 3'); echo $stack->pop() . PHP_EOL; echo $stack->pop() . PHP_EOL; echo $stack->pop() . PHP_EOL; Outputs: $ /usr/bin/php ./spl_stack01.php Message 3 Message 2 Message 1 44
    45. 45. SplHeap • SplHeap is an abstract class - SplMinHeap implements SplHeap (low » high) - SplMaxHeap implements SplHeap (high » low) • stacking values w/o FIFO, FILO order 45
    46. 46. Simple SplHeap example <?php $heap = new SplMinHeap; $heap->insert(5); $heap->insert(2); $heap->insert(8); $heap->insert(6); $heap->top(); while ($heap->valid()) { echo $heap->key() . ': ' . $heap->current() . PHP_EOL; $heap->next(); } //outputs 3: 2 2: 5 1: 6 0: 8 46
    47. 47. JupilerLeague with SplHeap <?php class JupilerLeague extends SplHeap { public function compare($array1, $array2) { $values1 = array_values($array1); $values2 = array_values($array2); if ($values1[0] === $values2[0]) return 0; return $values1[0] < $values2[0] ? -1 : 1; } } $heap = new JupilerLeague(); $heap->insert(array ('AA Gent' => 15)); $heap->insert(array ('Anderlecht' => 20)); $heap->insert(array ('Cercle Brugge' => 11)); $heap->insert(array ('Charleroi' => 12)); $heap->insert(array ('Club Brugge' => 21)); $heap->insert(array ('G. Beerschot' => 15)); $heap->insert(array ('Kortrijk' => 10)); $heap->insert(array ('KV Mechelen' => 18)); $heap->insert(array ('Lokeren' => 10)); $heap->insert(array ('Moeskroen' => 7)); $heap->insert(array ('Racing Genk' => 11)); $heap->insert(array ('Roeselare' => 6)); $heap->insert(array ('Standard' => 20)); $heap->insert(array ('STVV' => 17)); $heap->insert(array ('Westerlo' => 10)); $heap->insert(array ('Zulte Waregem' => 15)); $heap->top(); while ($heap->valid()) { list ($team, $score) = each ($heap->current()); echo $team . ': ' . $score . PHP_EOL; $heap->next(); } 47
    48. 48. JupilerLeague Scoreboard Club Brugge: 21 Anderlecht: 20 Standard: 20 KV Mechelen: 18 STVV: 17 Zulte Waregem: 15 AA Gent: 15 G. Beerschot: 15 Charleroi: 12 Racing Genk: 11 Cercle Brugge: 11 Kortrijk: 10 Lokeren: 10 Westerlo: 10 Moeskroen: 7 Roeselare: 6 48
    49. 49. Conclusion SPL can help you solve common PHP issues it’s built-in, so why not use it it requires no “advanced skills” to use 49
    50. 50. SPL is not all good • Matthew “Elazar” Turland pointed out: - Performance could be better (SPLStack) - ArrayObject doesn’t support all array functions • See his presentation: http://ishouldbecoding.com/publications 50
    51. 51. Not a bridge too far... 51
    52. 52. Only one minor thing... 52
    53. 53. Documentation problem • php.net/spl needs more documentation ! • you can help on that part: - see http://elizabethmariesmith.com/2009/02/setting-up-phd-on-windows - you can set up phpdoc on each platform ! - Efnet: #php.doc channel - http://doc.php.net - phpdoc@php.net 53
    54. 54. More about SPL... • main SPL documentation: http://php.net/spl • PHPro Tutorials on SPL: http://www.phpro.org/tutorials/Introduction-to-SPL.html • Lorenzo Alberton’s blog: http://www.alberton.info/php_5.3_spl_data_structures.html • http://www.colder.ch/news/01-08-2009/34/splobjectstorage-for- a-fa.html 54
    55. 55. License This presentation is released under the Creative Commons Attribution-Share Alike 3.0 Unported License You are free: - to share : to copy, distribute and transmit the work - to remix : to adapt the work Under the following conditions: - attribution : You must attribute the work in the manner specified by the author or licensor - share alike : If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license See: http://creativecommons.org/licenses/by-sa/3.0/ 55
    56. 56. Photo credits Marcus Boerger - Sebastian Bergmann http://www.flickr.com/photos/sebastian_bergmann/173891817 Golden Gate Bridge - Felix De Vliegher http://www.flickr.com/photos/felixdv/2883471022/ 56
    57. 57. Thank you Find my slides on http://slideshare.net/DragonBe Don’t forget to vote ! http://joind.in/613 57

    ×