phpunconf14: apigility einführung
DESCRIPTION
Eine Einführung in Apigility, das auf dem Zend Framework 2 basiert und mit dem man schnell mal eben seine API zusammen klicken kann.TRANSCRIPT
![Page 1: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/1.jpg)
APIGILITYAPIGILITYHeute klicken wir unsere API einfach mal zusammen...
1 / 54
![Page 2: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/2.jpg)
Über Ralf EggertÜber Ralf Eggert● PHP seit 1999● Zend Framework 2● Trainer ● Berater● Autor● Insulaner
2 / 54
![Page 3: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/3.jpg)
[A]
PrologProlog
3 / 54
![Page 4: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/4.jpg)
[1]
Neue APIin 2 Stunden?
4 / 54
![Page 5: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/5.jpg)
[2]
5 / 54
![Page 6: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/6.jpg)
[3]
In a NutshellIn a Nutshell
6 / 54
![Page 7: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/7.jpg)
Restful Web ServicesRestful Web Services
CLIENT
Web Browser
PHP
Javascript
RUBY
PYTHON
REST SERVER
/api/user/123
/api/user
/api/user
/api/user/123
/api/user/123
USER DOMAIN
getUserEntity()
getUserCollection()
addUserEntity()
updateUserEntity()
deleteUserEntity()
GET Request
JSON Response
GET Request
JSON Response
POST Request
JSON Response
PUT Request
JSON Response
DELETE Request
JSON Response
Integer
UserEntity
void
UserCollection
Array
Boolean
Integer, Array
Boolean
Integer
Boolean
7 / 54
![Page 8: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/8.jpg)
RPC Web ServicesRPC Web ServicesLocalCLIENT
javascript
RPCClient
JSON
Method:getUserParams:id
USER DOMAIN
getUserEntity()
GET Request
JSON Result
Integer
UserEntity
RPCServer
/json-rpc.php
Remote Call
JSON Result
javascriptJSON
Method:addUserParams:name
addUserEntity()POST Request
JSON Result
Array
Boolean
/json-rpc.phpRemote Call
JSON Result
javascriptXML
Method:getUserParams:id
getUserEntity()
GET Request
XML Result
Integer
UserEntity
/xml-rpc.php
Remote Call
XML Result
javascriptXML
Method:addUserParams:name
addUserEntity()
POST Request
XML Result
Array
Boolean
/xml-rpc.php
Remote Call
XML Result
8 / 54
![Page 9: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/9.jpg)
VersionierungVersionierung
default Version per URLdefault Version per URL
Version 1 per URLVersion 1 per URL
Version 2 per URLVersion 2 per URL
default Version per Content Negotiationdefault Version per Content Negotiation
Version 1 per Content NegotiationVersion 1 per Content Negotiation
Version 2 per Content NegotiationVersion 2 per Content Negotiation
9 / 54
![Page 10: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/10.jpg)
JSON / HAL / ProblemJSON / HAL / Problem
10 / 54
![Page 11: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/11.jpg)
WEITERE FEATURESWEITERE FEATURES[4, 5, 6, 7, 8, 9]
Datenbank-basiertDatenbank-basiert Code-basiertCode-basiert authentifizierungauthentifizierung
API DokumentationAPI Dokumentation DatenvalidierungDatenvalidierung DeploymentDeployment
11 / 54
![Page 12: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/12.jpg)
ModularisierungModularisierungZf-apigilityZf-apigility Zf-apigility-adminZf-apigility-admin Zf-apigility-documentationZf-apigility-documentation
Zf-apigility-documentation-swaggerZf-apigility-documentation-swagger Zf-apigility-providerZf-apigility-provider Zf-apigility-WelcomeZf-apigility-Welcome
Zf-api-problemZf-api-problem Zf-configurationZf-configuration Zf-content-negotiationZf-content-negotiation
Zf-content-validationZf-content-validation Zf-deployZf-deploy Zf-development-modeZf-development-mode
Zf-halZf-hal Zf-mvc-authZf-mvc-auth Zf-oauth2Zf-oauth2
Zf-restZf-rest Zf-RPCZf-RPC Zf-versioningZf-versioning
12 / 54
![Page 13: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/13.jpg)
InstallationInstallationComposer direkt$ curl -s https://getcomposer.org/installer | php --$ php composer.phar create-project -sdev zfcampus/zf-apigility-skeleton /path/to/install$ cd /path/to/install
ZIP und Composer$ wget https://github.com/zfcampus/zf-apigility-skeleton/archive/master.zip$ unzip -d /path/to/install master.zip $ cd /path/to/install$ php composer.phar install
Git und Composer$ git clone https://github.com/zfcampus/zf-apigility-skeleton.git /path/to/install$ cd /path/to/install$ php composer.phar install
Entwicklungsmodus einschalten und Rechte setzen$ php public/index.php development enable$ sudo chmod 777 -R config/$ sudo chmod 777 -R data/$ sudo chmod 777 -R module/
13 / 54
![Page 14: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/14.jpg)
StartseiteStartseite
14 / 54
![Page 15: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/15.jpg)
[4]
DB-basierterDB-basierterREST-ServiceREST-Service
15 / 54
![Page 16: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/16.jpg)
DatenbankmodellDatenbankmodell
16 / 54
![Page 17: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/17.jpg)
DatenbankadapterDatenbankadapter
Schritte 1 bis 617 / 54
![Page 18: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/18.jpg)
Rest Service, DB-basiertRest Service, DB-basiert
Schritte 1 bis 618 / 54
![Page 19: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/19.jpg)
Rest Service, DB-basiertRest Service, DB-basiert
Schritte 7 bis 1219 / 54
![Page 20: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/20.jpg)
Rest Service, DB-basiertRest Service, DB-basiert
Schritte 13 bis 1820 / 54
![Page 21: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/21.jpg)
REST Service User ListeREST Service User Liste
21 / 54
![Page 22: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/22.jpg)
REST Service User EntityREST Service User Entity
22 / 54
![Page 23: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/23.jpg)
[10]
POSTPOSTPUTPUT
DELETEDELETE23 / 54
![Page 24: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/24.jpg)
Rest Service Post IRest Service Post I
Schritte 1 bis 324 / 54
![Page 25: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/25.jpg)
Rest Service Post IIRest Service Post II
Schritte 4 bis 625 / 54
![Page 26: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/26.jpg)
Rest Service Put IRest Service Put I
Schritte 1 bis 326 / 54
![Page 27: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/27.jpg)
Rest Service Put IIRest Service Put II
Schritt 427 / 54
![Page 28: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/28.jpg)
Rest Service DeleteRest Service Delete
Schritte 1 bis 328 / 54
![Page 29: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/29.jpg)
[6]
AUTHENTIFI-AUTHENTIFI-ZIERUNGZIERUNG
29 / 54
![Page 30: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/30.jpg)
Authentifizierung IAuthentifizierung I
HTpasswd$ htpasswd -c data/htpasswd apigilityNew password: *********Re-type new password: *********Adding password for user apigility$
OAUTH2HTTP DIGESTHTTP BASIC
30 / 54
![Page 31: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/31.jpg)
Authentifizierung IIAuthentifizierung II
Schritte 1 bis 631 / 54
![Page 32: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/32.jpg)
Authentifizierung IIIAuthentifizierung III
Schritt 732 / 54
![Page 33: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/33.jpg)
Authentifizierung IVAuthentifizierung IV
Schritt 833 / 54
![Page 34: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/34.jpg)
[5]
Code-basierterCode-basierterREST SERVICEREST SERVICE
34 / 54
![Page 35: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/35.jpg)
Rest Service, Code-basiertRest Service, Code-basiert
Schritte 1 bis 635 / 54
![Page 36: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/36.jpg)
Rest Service, Code-basiertRest Service, Code-basiert
Schritte 7 bis 1236 / 54
![Page 37: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/37.jpg)
[11]
Was ist daran Code-basiert?
37 / 54
![Page 38: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/38.jpg)
Generierte DateienGenerierte DateienDateirechte setzen und Dateien anzeigen$ cd /home/devhost/apigility.local/$ sudo chmod 777 -R module/User/src/User/V2$ ls -al module/User/src/User/V2/Rest/UserProfile/drwxrwxrwx 2 www-data www-data 4096 Aug 30 21:39 .drwxrwxrwx 4 www-data www-data 4096 Aug 30 21:39 ..-rwxrwxrwx 1 www-data www-data 126 Aug 30 21:39 UserProfileCollection.php-rwxrwxrwx 1 www-data www-data 73 Aug 30 21:39 UserProfileEntity.php-rwxrwxrwx 1 www-data www-data 177 Aug 30 21:39 UserProfileResourceFactory.php-rwxrwxrwx 1 www-data www-data 2341 Aug 30 21:39 UserProfileResource.php
Verzeichnis für table Gateway Klassen anlegen$ cd module/User/src/User/V2/Rest/UserProfile/$ mkdir Table
38 / 54
![Page 39: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/39.jpg)
User Profile entityUser Profile entitynamespace User\V2\Rest\UserProfile;
class UserProfileEntity{ protected $id; protected $name; protected $email; protected $contacts; protected $websites;
public function setId($id) {} public function getId() {}
public function setName($name) {} public function getName() {}
public function setEmail($email) {} public function getEmail() {}
public function setContacts(array $contacts) {} public function getContacts() {}
public function setWebsites(array $websites) {} public function getWebsites() {}}
39 / 54
![Page 40: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/40.jpg)
User Table IUser Table Inamespace User\V2\Rest\UserProfile\Table;
use Zend\Db\Adapter\AdapterInterface;use Zend\Db\ResultSet\ResultSetInterface;use Zend\Db\TableGateway\TableGateway;
class UserTable extends TableGateway{ public function __construct( AdapterInterface $adapter, ResultSetInterface $resultSetPrototype = null ) { $table = 'users';
parent::__construct($table, $adapter, null, $resultSetPrototype); }
public function fetchUserById($id) { $select = $this->getSql()->select(); $select->where->equalTo('id', $id);
return $this->selectWith($select)->current(); }
[...]}
40 / 54
![Page 41: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/41.jpg)
User Table IIUser Table IInamespace User\V2\Rest\UserProfile\Table;
class UserTable extends TableGateway{ [...]
public function fetchContactsById($id) { $select = $this->getSql()->select(); $select->join('user_contacts', 'user_id_2 = id', array()); $select->where->equalTo('user_id_1', $id);
return $this->selectWith($select)->toArray(); }
public function fetchUsers($params) { $select = $this->getSql()->select();
return $this->selectWith($select)->toArray(); }}
41 / 54
![Page 42: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/42.jpg)
User Table factoryUser Table factorynamespace User\V2\Rest\UserProfile\Table;
use Zend\Db\ResultSet\ResultSet;use Zend\ServiceManager\FactoryInterface;use Zend\ServiceManager\ServiceLocatorInterface;
class UserTableFactory implements FactoryInterface{ public function createService(ServiceLocatorInterface $serviceLocator) { $dbAdapter = $serviceLocator->get('MysqlAdapter');
$resultSet = new ResultSet(ResultSet::TYPE_ARRAY);
$table = new UserTable($dbAdapter, $resultSet);
return $table; }}
42 / 54
![Page 43: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/43.jpg)
Website TableWebsite Tablenamespace User\V2\Rest\UserProfile\Table;
use Zend\Db\Adapter\AdapterInterface;use Zend\Db\ResultSet\ResultSetInterface;use Zend\Db\TableGateway\TableGateway;
class WebsiteTable extends TableGateway{ public function __construct( AdapterInterface $adapter, ResultSetInterface $resultSetPrototype = null ) { $table = 'websites';
parent::__construct($table, $adapter, null, $resultSetPrototype); }
public function fetchWebsitesById($id) { $select = $this->getSql()->select(); $select->join('user_websites', 'website_id = id', array()); $select->where->equalTo('user_id', $id);
return $this->selectWith($select)->toArray(); }}
43 / 54
![Page 44: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/44.jpg)
Website FactoryWebsite Factorynamespace User\V2\Rest\UserProfile\Table;
use Zend\Db\ResultSet\ResultSet;use Zend\ServiceManager\FactoryInterface;use Zend\ServiceManager\ServiceLocatorInterface;
class WebsiteTableFactory implements FactoryInterface{ public function createService(ServiceLocatorInterface $serviceLocator) { $dbAdapter = $serviceLocator->get('MysqlAdapter');
$resultSet = new ResultSet(ResultSet::TYPE_ARRAY);
$table = new WebsiteTable($dbAdapter, $resultSet);
return $table; }
}
44 / 54
![Page 45: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/45.jpg)
User Profile Resource IUser Profile Resource Inamespace User\V2\Rest\UserProfile;
use User\V2\Rest\UserProfile\Table\UserTable;use User\V2\Rest\UserProfile\Table\WebsiteTable;use ZF\ApiProblem\ApiProblem;use ZF\Rest\AbstractResourceListener;
class UserProfileResource extends AbstractResourceListener{ protected $userTable; protected $websiteTable;
public function setUserTable($userTable) {} public function getUserTable() {} public function setWebsiteTable($websiteTable) {} public function getWebsiteTable() {}
protected function addContactsAndWebsites(array $user) { $user['contacts'] = $this->getUserTable()->fetchContactsById($user['id']); $user['websites'] = $this->getWebsiteTable()->fetchWebsitesById($user['id']);
return $user; }
[...]}
45 / 54
![Page 46: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/46.jpg)
User Profile Resource IIUser Profile Resource IInamespace User\V2\Rest\UserProfile;
class UserProfileResource extends AbstractResourceListener{ [...]
public function create($data) { return new ApiProblem(405, 'The POST method has not been defined'); }
public function delete($id) {} public function deleteList($data) {}
public function fetch($id) { $user = $this->getUserTable()->fetchUserById($id);
if (!$user) { return new ApiProblem(404, 'User profile for id ' . $id . ' not found'); }
return $this->addContactsAndWebsites($user); }
[...]}
46 / 54
![Page 47: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/47.jpg)
User Profile Resource IIIUser Profile Resource IIInamespace User\V2\Rest\UserProfile;
class UserProfileResource extends AbstractResourceListener{ [...]
public function fetchAll($params = array()) { $users = $this->getUserTable()->fetchUsers($params);
if (!$users) { return new ApiProblem(404, 'No user profiles found'); }
foreach ($users as $key => $user) { $users[$key] = $this->addContactsAndWebsites($user); }
return $users; }
public function patch($id, $data) {} public function replaceList($data) {} public function update($id, $data) {}}
47 / 54
![Page 48: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/48.jpg)
User Profile Resource FactoryUser Profile Resource Factorynamespace User\V2\Rest\UserProfile;
class UserProfileResourceFactory{ public function __invoke($services) { $userTable = $services->get( 'User\\V2\\Rest\\UserProfile\\Table\\UserTable' ); $websiteTable = $services->get( 'User\\V2\\Rest\\UserProfile\\Table\\WebsiteTable' );
$resource = new UserProfileResource(); $resource->setUserTable($userTable); $resource->setWebsiteTable($websiteTable);
return $resource; }}
48 / 54
![Page 49: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/49.jpg)
User Profile Module ConfigUser Profile Module Configreturn array( [...]
'service_manager' => array( 'factories' => array( 'User\\V2\\Rest\\UserProfile\\UserProfileResource' => 'User\\V2\\Rest\\UserProfile\\UserProfileResourceFactory', 'User\\V2\\Rest\\UserProfile\\Table\\UserTable' => 'User\\V2\\Rest\\UserProfile\\Table\\UserTableFactory', 'User\\V2\\Rest\\UserProfile\\Table\\WebsiteTable' => 'User\\V2\\Rest\\UserProfile\\Table\\WebsiteTableFactory', ), ),);
49 / 54
![Page 50: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/50.jpg)
REST Service ProfilREST Service Profil
50 / 54
![Page 51: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/51.jpg)
REST Service ProfilListeREST Service ProfilListe
51 / 54
![Page 52: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/52.jpg)
[A]
EPILOGEPILOG
52 / 54
![Page 53: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/53.jpg)
API ZUSAMMENKLICKENAPI ZUSAMMENKLICKENKlappt für einfache APIs, ansonsten ist Handarbeit erforderlich...
Repository: https://github.com/RalfEggert/phpughh-apigility
53 / 54
![Page 54: PHPunconf14: Apigility Einführung](https://reader036.vdokument.com/reader036/viewer/2022062606/5538969f4a795977028b47b3/html5/thumbnails/54.jpg)
BildnachweisBildnachweis[A] Fotos von Ralf Eggert
[1] Still here https://www.flickr.com/photos/thenovys/3784261365 von Abe Novy - CC-BY https://creativecommons.org/licenses/by/2.0/
[2] Young student https://www.flickr.com/photos/audiolucistore/14159712431/ von www.audio-luci-store.it - CC-BY https://creativecommons.org/licenses/by/2.0/
[3] Acorns https://www.flickr.com/photos/dno1967b/5431273344 von Daniel Oines - CC-BY https://creativecommons.org/licenses/by/2.0/
[4] Fixing the database https://www.flickr.com/photos/dahlstroms/4140461901 von Håkan Dahlström - CC-BY https://creativecommons.org/licenses/by/2.0/
[5] Monaco 14pt https://www.flickr.com/photos/polarity/3138680190 von Robert Agthe - CC-BY https://creativecommons.org/licenses/by/2.0/
[6] RSA Securid Token - Credit Card Style https://www.flickr.com/photos/purpleslog/265657780 von Purple Slog - CC-BY https://creativecommons.org/licenses/by/2.0/
[7] Shelf of Used Books https://www.flickr.com/photos/thedarkthing/5363586197 von William Ross - CC-BY https://creativecommons.org/licenses/by/2.0/
[8] Ticket validator at Nice train station https://www.flickr.com/photos/traveleden/3797157077 von Simon - CC-BY https://creativecommons.org/licenses/by/2.0/
[9] Test Lab - Supermicro Storage https://www.flickr.com/photos/jemimus/8533890844 von Robert - CC-BY https://creativecommons.org/licenses/by/2.0/
[10] We are all fan of laptops https://www.flickr.com/photos/scottvanderchijs/4493248747 von Scott & Elaine van der Chijs - CC-BY https://creativecommons.org/licenses/by/2.0/
[11] Surprise https://www.flickr.com/photos/photographybycolby/11927931295 von Colby Stopa - CC-BY https://creativecommons.org/licenses/by/2.0/
Alle weiteren Screenshots wurden von Ralf Eggert erstellt.
54 / 54