├── .idea ├── .name ├── misc.xml ├── php.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml ├── codeStyleSettings.xml └── restapi-example.iml ├── Packages └── Application │ ├── Helmich.Products │ ├── Classes │ │ └── Helmich │ │ │ └── Products │ │ │ ├── Domain │ │ │ ├── Repository │ │ │ │ ├── ProductRepository.php │ │ │ │ └── ManufacturerRepository.php │ │ │ └── Model │ │ │ │ ├── Manufacturer.php │ │ │ │ ├── InventoryChange.php │ │ │ │ └── Product.php │ │ │ └── Command │ │ │ └── ExampleDataCommandController.php │ └── composer.json │ ├── Helmich.ProductsApiSimple │ ├── composer.json │ ├── Configuration │ │ ├── Routes.Entity.yaml │ │ └── Routes.yaml │ └── Classes │ │ └── Helmich │ │ └── ProductsApiSimple │ │ └── Controller │ │ ├── ManufacturerController.php │ │ └── ProductController.php │ └── Helmich.ProductsApiAdvanced │ ├── composer.json │ ├── Configuration │ ├── Routes.Manufacturer.yaml │ ├── Routes.Product.yaml │ └── Routes.yaml │ └── Classes │ └── Helmich │ └── ProductsApiAdvanced │ ├── Dto │ ├── ManufacturerReferenceDto.php │ ├── ManufacturerDto.php │ └── ProductDto.php │ ├── Controller │ ├── InventoryChangeController.php │ ├── ManufacturerController.php │ └── ProductController.php │ ├── Normalizer │ ├── ManufacturerNormalizer.php │ ├── InventoryChangeNormalizer.php │ ├── Decorator │ │ ├── ProductHypermediaDecorator.php │ │ └── ManufacturerHypermediaDecorator.php │ └── ProductNormalizer.php │ └── Mapper │ └── ProductMapper.php ├── .gitignore ├── LICENSE.txt ├── composer.json ├── Configuration └── Routes.yaml ├── README.md └── README.de.md /.idea/.name: -------------------------------------------------------------------------------- 1 | restapi-example -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.Products/Classes/Helmich/Products/Domain/Repository/ProductRepository.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Configuration/Routes.Manufacturer.yaml: -------------------------------------------------------------------------------- 1 | - name: List all 2 | uriPattern: '' 3 | defaults: 4 | @action: list 5 | httpMethods: 6 | - GET 7 | 8 | - name: Show one 9 | uriPattern: '/{manufacturer}' 10 | defaults: 11 | @action: show 12 | httpMethods: 13 | - GET 14 | 15 | - name: List by manufacturer 16 | uriPattern: '/{manufacturer}/products' 17 | defaults: 18 | @controller: Product 19 | @action: listByManufacturer 20 | httpMethods: 21 | - GET 22 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Dto/ManufacturerReferenceDto.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 19 | } 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function getIdentifier() { 25 | return $this->identifier; 26 | } 27 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiSimple/Configuration/Routes.Entity.yaml: -------------------------------------------------------------------------------- 1 | - name: List all s 2 | uriPattern: '' 3 | defaults: 4 | @action: list 5 | httpMethods: 6 | - GET 7 | 8 | - name: Create new 9 | uriPattern: '' 10 | defaults: 11 | @action: create 12 | httpMethods: 13 | - POST 14 | 15 | - name: Show 16 | uriPattern: '/{}' 17 | defaults: 18 | @action: show 19 | httpMethods: 20 | - GET 21 | 22 | - name: Update 23 | uriPattern: '/{.__identity}' 24 | defaults: 25 | @action: update 26 | httpMethods: 27 | - PUT 28 | 29 | - name: Delete 30 | uriPattern: '/{}' 31 | defaults: 32 | @action: delete 33 | httpMethods: 34 | - DELETE -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Configuration/Routes.Product.yaml: -------------------------------------------------------------------------------- 1 | - name: List all 2 | uriPattern: '' 3 | defaults: 4 | @action: list 5 | httpMethods: 6 | - GET 7 | 8 | - name: Create 9 | uriPattern: '' 10 | defaults: 11 | @action: create 12 | httpMethods: 13 | - POST 14 | 15 | - name: Show one 16 | uriPattern: '/{product}' 17 | defaults: 18 | @action: show 19 | httpMethods: 20 | - GET 21 | 22 | - name: Update 23 | uriPattern: '/{productIdentifier}' 24 | defaults: 25 | @action: update 26 | httpMethods: 27 | - PUT 28 | 29 | - name: Delete 30 | uriPattern: '/{product}' 31 | defaults: 32 | @action: delete 33 | httpMethods: 34 | - DELETE 35 | 36 | - name: List inventory changes 37 | uriPattern: '/{product}/inventory-changes' 38 | defaults: 39 | @controller: InventoryChange 40 | @action: list 41 | httpMethods: 42 | - GET -------------------------------------------------------------------------------- /Packages/Application/Helmich.Products/Classes/Helmich/Products/Domain/Model/Manufacturer.php: -------------------------------------------------------------------------------- 1 | name = $name; 29 | $this->location = $location; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getName() { 36 | return $this->name; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getLocation() { 43 | return $this->location; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Controller/InventoryChangeController.php: -------------------------------------------------------------------------------- 1 | registerNormalizerForClass(InventoryChange::class, new InventoryChangeNormalizer()); 16 | } 17 | } 18 | 19 | public function listAction(Product $product) { 20 | $changes = $product->getInventoryChanges(); 21 | $this->view->assign('inventoryChanges', $changes); 22 | } 23 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Normalizer/ManufacturerNormalizer.php: -------------------------------------------------------------------------------- 1 | $this->persistenceManager->getIdentifierByObject($object), 25 | 'name' => $object->getName(), 26 | 'location' => $object->getLocation() 27 | ]; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Normalizer/InventoryChangeNormalizer.php: -------------------------------------------------------------------------------- 1 | persistenceManager->getIdentifierByObject($object); 20 | return [ 21 | 'identifier' => $identifier, 22 | 'quantity' => $object->getAmount(), 23 | 'date' => $object->getDate()->format('r') 24 | ]; 25 | } 26 | throw new \InvalidArgumentException('Expected instanceof ' . InventoryChange::class . ', got ' . get_class($object) . '!'); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Normalizer/Decorator/ProductHypermediaDecorator.php: -------------------------------------------------------------------------------- 1 | uriBuilder = $uriBuilder; 17 | $this->actual = $actual; 18 | } 19 | 20 | public function objectToScalar($object) { 21 | $scalar = $this->actual->objectToScalar($object); 22 | $scalar['href'] = $this->uriBuilder->reset()->uriFor('show', ['product' => $object], 'Product'); 23 | $scalar['links'] = [ 24 | [ 25 | 'rel' => 'inventoryChanges', 26 | 'href' => $this->uriBuilder->reset()->uriFor('list', ['product' => $object], 'InventoryChange') 27 | ] 28 | ]; 29 | 30 | return $scalar; 31 | } 32 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.Products/Classes/Helmich/Products/Command/ExampleDataCommandController.php: -------------------------------------------------------------------------------- 1 | productRepository->add($product); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Helmich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Normalizer/ProductNormalizer.php: -------------------------------------------------------------------------------- 1 | persistenceManager->getIdentifierByObject($object); 21 | return [ 22 | 'identifier' => $identifier, 23 | 'name' => $object->getName(), 24 | 'quantity' => $object->getQuantity(), 25 | 'price' => $object->getPrice(), 26 | 'manufacturer' => $object->getManufacturer() 27 | ]; 28 | } 29 | throw new \InvalidArgumentException('Expected instanceof ' . Product::class . ', got ' . get_class($object) . '!'); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helmich/flow-restapi-example", 3 | "description": "Example application for REST webservices with TYPO3 Flow", 4 | "license": "MIT", 5 | "config": { 6 | "vendor-dir": "Packages/Libraries", 7 | "bin-dir": "bin" 8 | }, 9 | "require": { 10 | "typo3/flow": "2.3.*", 11 | "typo3/welcome": "2.3.*", 12 | "doctrine/migrations": "@dev", 13 | "symfony/serializer": "~2.6", 14 | "helmich/flow-resttools": "dev-master" 15 | }, 16 | "require-dev": { 17 | "typo3/kickstart": "2.3.*", 18 | "typo3/buildessentials": "2.3.*", 19 | "phpunit/phpunit": "4.0.*", 20 | "mikey179/vfsstream": "1.2.*" 21 | }, 22 | "suggest": { 23 | "ext-pdo_sqlite": "For running functional tests out-of-the-box this is required" 24 | }, 25 | "scripts": { 26 | "post-update-cmd": "TYPO3\\Flow\\Composer\\InstallerScripts::postUpdateAndInstall", 27 | "post-install-cmd": "TYPO3\\Flow\\Composer\\InstallerScripts::postUpdateAndInstall", 28 | "post-package-update": "TYPO3\\Flow\\Composer\\InstallerScripts::postPackageUpdateAndInstall", 29 | "post-package-install": "TYPO3\\Flow\\Composer\\InstallerScripts::postPackageUpdateAndInstall" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Normalizer/Decorator/ManufacturerHypermediaDecorator.php: -------------------------------------------------------------------------------- 1 | uriBuilder = $uriBuilder; 20 | $this->actual = $actual; 21 | $this->withLinks = $withLinks; 22 | } 23 | 24 | public function objectToScalar($object) { 25 | $scalar = $this->actual->objectToScalar($object); 26 | $scalar['href'] = $this->uriBuilder->reset()->uriFor('show', ['manufacturer' => $object], 'Manufacturer'); 27 | 28 | if ($this->withLinks) { 29 | $scalar['links'] = [ 30 | [ 31 | 'rel' => 'products', 32 | 'href' => $this->uriBuilder->reset()->uriFor('listByManufacturer', ['manufacturer' => $object], 'Product') 33 | ] 34 | ]; 35 | } 36 | 37 | return $scalar; 38 | } 39 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Dto/ManufacturerDto.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 39 | $this->name = $name; 40 | $this->location = $location; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getIdentifier() { 47 | return $this->identifier; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getName() { 54 | return $this->name; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getLocation() { 61 | return $this->location; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiSimple/Configuration/Routes.yaml: -------------------------------------------------------------------------------- 1 | # # 2 | # Routes configuration # 3 | # # 4 | # This file contains the configuration for the MVC router. # 5 | # Just add your own modifications as necessary. # 6 | # # 7 | # Please refer to the Flow manual for possible configuration options. # 8 | # # 9 | 10 | - name: 'Products' 11 | uriPattern: 'products(.{@format})' 12 | defaults: 13 | @package: Helmich.ProductsApiSimple 14 | @controller: Product 15 | @format: json 16 | subRoutes: 17 | ProductSubroutes: 18 | package: Helmich.ProductsApiSimple 19 | suffix: Entity 20 | variables: 21 | entity: product 22 | 23 | - name: 'Manufacturers' 24 | uriPattern: 'manufacturers(.{@format})' 25 | defaults: 26 | @package: Helmich.ProductsApiSimple 27 | @controller: Manufacturer 28 | @format: json 29 | subRoutes: 30 | ManufacturerSubroutes: 31 | package: Helmich.ProductsApiSimple 32 | suffix: Entity 33 | variables: 34 | entity: manufacturer -------------------------------------------------------------------------------- /Configuration/Routes.yaml: -------------------------------------------------------------------------------- 1 | # # 2 | # Routes configuration # 3 | # # 4 | # This file contains the configuration for the MVC router. # 5 | # Just add your own modifications as necessary. # 6 | # # 7 | # Please refer to the Flow manual for possible configuration options. # 8 | # # 9 | 10 | ## 11 | # Routes for the two packages providing REST APIs for the products 12 | # domain model. Note how you can use route prefixes (like "api/v1") 13 | # to add different versions of your API. 14 | # 15 | 16 | - name: 'Products v1' 17 | uriPattern: 'api/v1/' 18 | subRoutes: 19 | ProductsSimpleSubroutes: 20 | package: Helmich.ProductsApiSimple 21 | 22 | - name: 'Products v2' 23 | uriPattern: 'api/v2/' 24 | subRoutes: 25 | ProductsAdvancedSubroutes: 26 | package: Helmich.ProductsApiAdvanced 27 | 28 | ## 29 | # Flow subroutes 30 | # 31 | 32 | - 33 | name: 'Flow' 34 | uriPattern: '' 35 | defaults: 36 | '@format': 'html' 37 | subRoutes: 38 | FlowSubroutes: 39 | package: TYPO3.Flow 40 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.Products/Classes/Helmich/Products/Domain/Model/InventoryChange.php: -------------------------------------------------------------------------------- 1 | product = $product; 41 | $this->amount = $amount; 42 | $this->purpose = $purpose; 43 | $this->date = new \DateTime(); 44 | } 45 | 46 | /** 47 | * @return Product 48 | */ 49 | public function getProduct() { 50 | return $this->product; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function getAmount() { 57 | return $this->amount; 58 | } 59 | 60 | /** 61 | * @return \DateTime 62 | */ 63 | public function getDate() { 64 | return $this->date; 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RESTful Webservices with TYPO3 Flow 2 | =================================== 3 | 4 | Martin Helmich 5 | 6 | Synopsis 7 | -------- 8 | 9 | This repository contains an example application that demonstrates on how 10 | to implement RESTful Webservices with TYPO3 Flow. It contains three packages: 11 | 12 | - `Helmich.Products` contains entity classes and repositories around a simple inventory management domain model 13 | - `Helmich.ProductsApiSimple` contains a simple REST-like API (level 2 of the 14 | [Richardson Maturity Model](http://martinfowler.com/articles/richardsonMaturityModel.html)) 15 | which allows you to CRUD products and manufacturers using respective HTTP methods. 16 | - `Helmich.ProductsApiAdvanced` contains an advanced API (level 3 of the RMM), which is _hypermedia controlled_ 17 | (which means that there are links between resources) and also shows some design patterns to decouple your 18 | REST resource representations from your domain entities. 19 | 20 | Installation 21 | ------------ 22 | 23 | You can install this application using [Composer](http://getcomposer.org): 24 | 25 | composer create-project helmich/flow-restapi-example 26 | 27 | Follow the [TYPO3 Flow installation instructions](http://docs.typo3.org/flow/TYPO3FlowDocumentation/Quickstart/Index.html#installing-typo3-flow) 28 | in all other ways. Also [set up your database](http://docs.typo3.org/flow/TYPO3FlowDocumentation/Quickstart/Index.html#database-setup) 29 | and run the database migrations: 30 | 31 | ./flow doctrine:migrate 32 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiSimple/Classes/Helmich/ProductsApiSimple/Controller/ManufacturerController.php: -------------------------------------------------------------------------------- 1 | JsonView::class]; 17 | 18 | /** 19 | * @var ManufacturerRepository 20 | * @Flow\Inject 21 | */ 22 | protected $manufacturerRepository; 23 | 24 | protected function initializeView(ViewInterface $view) { 25 | if ($view instanceof JsonView) { 26 | $manufacturerConfiguration = [ 27 | '_exposeObjectIdentifier' => TRUE, 28 | '_exposedObjectIdentifierKey' => 'identifier' 29 | ]; 30 | $view->setConfiguration([ 31 | 'value' => [ 32 | 'manufacturers' => ['_descendAll' => $manufacturerConfiguration], 33 | 'manufacturer' => $manufacturerConfiguration 34 | ] 35 | ]); 36 | } 37 | } 38 | 39 | public function listAction() { 40 | $manufacturers = $this->manufacturerRepository->findAll(); 41 | $this->view->assign('value', ['manufacturers' => $manufacturers]); 42 | } 43 | 44 | public function showAction(Manufacturer $manufacturer) { 45 | $this->view->assign('value', ['manufacturer' => $manufacturer]); 46 | } 47 | } -------------------------------------------------------------------------------- /README.de.md: -------------------------------------------------------------------------------- 1 | RESTful Webservices mit TYPO3 Flow 2 | ================================== 3 | 4 | Martin Helmich 5 | 6 | Zusammenfassung 7 | --------------- 8 | 9 | Dieses Repository enthält eine Beispiel-Anwendung, die die Entwicklung von 10 | REST-Webservices mit TYPO3 Flow veranschaulicht. Sie enhält drei Pakete: 11 | 12 | - `Helmich.Products` enthält Entitäts-Klassen und Repositories um ein einfaches Fachmodell rund um eine Inventarverwaltung 13 | - `Helmich.ProductsApiSimple` enthält eine einfach REST-ähnliche API (Stufe 2 des 14 | [Richardson Maturity Model](http://martinfowler.com/articles/richardsonMaturityModel.html)), über die 15 | Produkte und Hersteller über die entsprechenden HTTP-Methoden ausgelesen, erstellt, verändert und gelöscht werden können. 16 | - `Helmich.ProductsApiAdvanced` enhält eine erweiterte API (Level 3 des RMM), deren Ressourcen mit Hyperlinks 17 | untereinander verknüpft sind. Sie zeigt außerdem einige Designmuster, mit denen die Ressourcen-Repräsentation 18 | des REST-Webservices von den Entitäten des Fachmodells entkoppelt werden kann. 19 | 20 | Installation 21 | ------------ 22 | 23 | Diese Anwendung kann über [Composer](http://getcomposer.org) installiert werden: 24 | 25 | composer create-project helmich/flow-restapi-example 26 | 27 | Folgen Sie anschließend den [Installationsanweisungen von TYPO3](http://docs.typo3.org/flow/TYPO3FlowDocumentation/Quickstart/Index.html#installing-typo3-flow). 28 | Richten Sie auch Ihre [Datenbank ein](http://docs.typo3.org/flow/TYPO3FlowDocumentation/Quickstart/Index.html#database-setup) 29 | und führen Sie die Doctrine-Migrationen aus: 30 | 31 | ./flow doctrine:migrate 32 | -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Controller/ManufacturerController.php: -------------------------------------------------------------------------------- 1 | registerNormalizerForClass(Manufacturer::class, new ManufacturerHypermediaDecorator(new ManufacturerNormalizer(), TRUE, $this->uriBuilder)); 30 | } 31 | } 32 | 33 | public function listAction() { 34 | $this->view->assign('manufacturers', $this->manufacturerRepository->findAll()); 35 | } 36 | 37 | public function showAction(Manufacturer $manufacturer) { 38 | $this->view->assign('manufacturer', $manufacturer); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Mapper/ProductMapper.php: -------------------------------------------------------------------------------- 1 | manufacturerRepository->findByIdentifier($productDto->getManufacturer()->getIdentifier()); 32 | $product = new Product( 33 | $productDto->getName(), 34 | $productDto->getPrice(), 35 | $manufacturer, 36 | $productDto->getQuantity() 37 | ); 38 | return $product; 39 | } 40 | 41 | public function updateProduct($identifier, ProductDto $productDto) { 42 | /** @var Product $product */ 43 | $product = $this->productRepository->findByIdentifier($identifier); 44 | $manufacturer = $this->manufacturerRepository->findByIdentifier($productDto->getManufacturer()->getIdentifier()); 45 | 46 | if (NULL === $product) { 47 | throw new \InvalidArgumentException('Product does not exist!'); 48 | } 49 | 50 | $product->setName($productDto->getName()); 51 | $product->setPrice($productDto->getPrice()); 52 | $product->setQuantity($productDto->getQuantity()); 53 | $product->setManufacturer($manufacturer); 54 | 55 | return $product; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Configuration/Routes.yaml: -------------------------------------------------------------------------------- 1 | # # 2 | # Routes configuration # 3 | # # 4 | # This file contains the configuration for the MVC router. # 5 | # Just add your own modifications as necessary. # 6 | # # 7 | # Please refer to the Flow manual for possible configuration options. # 8 | # # 9 | 10 | 11 | - name: 'Products' 12 | uriPattern: 'products(.{@format})' 13 | defaults: 14 | @package: Helmich.ProductsApiAdvanced 15 | @controller: Product 16 | @format: json 17 | subRoutes: 18 | ProductSubroutes: 19 | package: Helmich.ProductsApiAdvanced 20 | suffix: Product 21 | 22 | - name: 'Manufacturers' 23 | uriPattern: 'manufacturers(.{@format})' 24 | defaults: 25 | @package: Helmich.ProductsApiAdvanced 26 | @controller: Manufacturer 27 | @format: json 28 | subRoutes: 29 | ManufacturerSubroutes: 30 | package: Helmich.ProductsApiAdvanced 31 | suffix: Manufacturer 32 | 33 | #- name: 'List Products' 34 | # uriPattern: 'products(.{@format})' 35 | # defaults: 36 | # @package: Helmich.ProductsApiAdvanced 37 | # @controller: Product 38 | # @action: list 39 | # @format: json 40 | # httpMethods: [GET] 41 | # 42 | #- name: 'Create products' 43 | # uriPattern: products 44 | # defaults: 45 | # @package: Helmich.ProductsApiAdvanced 46 | # @controller: Product 47 | # @action: create 48 | # @format: json 49 | # httpMethods: [POST] 50 | # 51 | #- name: 'Show product' 52 | # uriPattern: 'products/{product}(.{@format})' 53 | # defaults: 54 | # @package: Helmich.ProductsApiAdvanced 55 | # @controller: Product 56 | # @action: show 57 | # @format: json 58 | # httpMethods: [GET] -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Dto/ProductDto.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 53 | $this->name = $name; 54 | $this->quantity = $quantity; 55 | $this->price = $price; 56 | $this->manufacturer = $manufacturer; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getIdentifier() { 63 | return $this->identifier; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getName() { 70 | return $this->name; 71 | } 72 | 73 | /** 74 | * @return int 75 | */ 76 | public function getQuantity() { 77 | return $this->quantity; 78 | } 79 | 80 | /** 81 | * @return float 82 | */ 83 | public function getPrice() { 84 | return $this->price; 85 | } 86 | 87 | /** 88 | * @return ManufacturerReferenceDto 89 | */ 90 | public function getManufacturer() { 91 | return $this->manufacturer; 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiSimple/Classes/Helmich/ProductsApiSimple/Controller/ProductController.php: -------------------------------------------------------------------------------- 1 | JsonView::class]; 17 | 18 | /** 19 | * @var ProductRepository 20 | * @Flow\Inject 21 | */ 22 | protected $productRepository; 23 | 24 | protected function initializeView(ViewInterface $view) { 25 | if ($view instanceof JsonView) { 26 | $productConfiguration = [ 27 | '_exposeObjectIdentifier' => TRUE, 28 | '_exposedObjectIdentifierKey' => 'identifier', 29 | '_descend' => [ 30 | 'manufacturer' => [ 31 | '_exclude' => ['__isInitialized__'], 32 | '_exposeObjectIdentifier' => TRUE, 33 | '_exposedObjectIdentifierKey' => 'identifier' 34 | ] 35 | ] 36 | ]; 37 | $view->setConfiguration([ 38 | 'value' => [ 39 | 'products' => ['_descendAll' => $productConfiguration], 40 | 'product' => $productConfiguration 41 | ] 42 | ]); 43 | } 44 | } 45 | 46 | public function listAction() { 47 | $products = $this->productRepository->findAll(); 48 | $this->view->assign('value', ['products' => $products]); 49 | } 50 | 51 | public function showAction(Product $product) { 52 | $this->view->assign('value', ['product' => $product]); 53 | } 54 | 55 | protected function initializeCreateAction() { 56 | $config = $this->arguments['product']->getPropertyMappingConfiguration(); 57 | $config->setTypeConverterOption('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, TRUE); 58 | $config->allowAllProperties(); 59 | } 60 | 61 | public function createAction(Product $product) { 62 | $this->productRepository->add($product); 63 | $this->response->setStatus(201); 64 | $this->view->assign('value', ['product' => $product]); 65 | } 66 | 67 | protected function initializeUpdateAction() { 68 | $config = $this->arguments['product']->getPropertyMappingConfiguration(); 69 | $config->setTypeConverterOption('TYPO3\Flow\Property\TypeConverter\PersistentObjectConverter', PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, TRUE); 70 | $config->allowAllProperties(); 71 | } 72 | 73 | public function updateAction(Product $product) { 74 | $this->productRepository->update($product); 75 | $this->view->assign('value', ['product' => $product]); 76 | } 77 | 78 | public function deleteAction(Product $product) { 79 | $this->productRepository->remove($product); 80 | $this->response->setStatus(204); 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.Products/Classes/Helmich/Products/Domain/Model/Product.php: -------------------------------------------------------------------------------- 1 | 33 | * @ORM\OneToMany(mappedBy="product", cascade={"persist", "remove"}) 34 | */ 35 | protected $inventoryChanges; 36 | 37 | /** 38 | * @var Manufacturer 39 | * @ORM\ManyToOne(cascade={"persist"}) 40 | */ 41 | protected $manufacturer; 42 | 43 | /** 44 | * Default constructor. 45 | * 46 | * @param string $name The product name. 47 | * @param float $price The product price. 48 | * @param Manufacturer $manufacturer The manufacturer. 49 | * @param int $quantity The initial quantity. 50 | */ 51 | public function __construct($name, $price, Manufacturer $manufacturer, $quantity = 0) { 52 | $this->name = $name; 53 | $this->quantity = $quantity; 54 | $this->price = $price; 55 | $this->manufacturer = $manufacturer; 56 | $this->inventoryChanges = new ArrayCollection(); 57 | 58 | if ($quantity != 0) { 59 | $this->inventoryChanges->add(new InventoryChange($this, $quantity, 'Initial stock')); 60 | } 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getName() { 67 | return $this->name; 68 | } 69 | 70 | /** 71 | * @param string $name 72 | */ 73 | public function setName($name) { 74 | $this->name = $name; 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getQuantity() { 81 | return $this->quantity; 82 | } 83 | 84 | /** 85 | * @param int $quantity 86 | */ 87 | public function setQuantity($quantity) { 88 | $difference = $quantity - $this->quantity; 89 | if ($difference !== 0) { 90 | if ($difference > 0) { 91 | $purpose = 'Added items to stock.'; 92 | } else { 93 | $purpose = 'Removed items from stock.'; 94 | } 95 | 96 | $this->addInventoryChange(new InventoryChange($this, $difference, $purpose)); 97 | } 98 | } 99 | 100 | /** 101 | * @return float 102 | */ 103 | public function getPrice() { 104 | return (float)$this->price; 105 | } 106 | 107 | /** 108 | * @param float $price 109 | */ 110 | public function setPrice($price) { 111 | $this->price = $price; 112 | } 113 | 114 | /** 115 | * @return Collection 116 | */ 117 | public function getInventoryChanges() { 118 | return $this->inventoryChanges; 119 | } 120 | 121 | /** 122 | * @param InventoryChange $inventoryChange 123 | */ 124 | public function addInventoryChange(InventoryChange $inventoryChange) { 125 | $this->quantity += $inventoryChange->getAmount(); 126 | $this->inventoryChanges->add($inventoryChange); 127 | } 128 | 129 | /** 130 | * @return Manufacturer 131 | */ 132 | public function getManufacturer() { 133 | return $this->manufacturer; 134 | } 135 | 136 | /** 137 | * @param Manufacturer $manufacturer 138 | */ 139 | public function setManufacturer(Manufacturer $manufacturer) { 140 | $this->manufacturer = $manufacturer; 141 | } 142 | 143 | 144 | } -------------------------------------------------------------------------------- /Packages/Application/Helmich.ProductsApiAdvanced/Classes/Helmich/ProductsApiAdvanced/Controller/ProductController.php: -------------------------------------------------------------------------------- 1 | registerNormalizerForClass(Product::class, new ProductHypermediaDecorator(new ProductNormalizer(), $this->uriBuilder)); 35 | $view->registerNormalizerForClass(Manufacturer::class, new ManufacturerHypermediaDecorator(new ManufacturerNormalizer(), FALSE, $this->uriBuilder)); 36 | } 37 | } 38 | 39 | public function listAction() { 40 | $this->view->assign('products', $this->productRepository->findAll()); 41 | } 42 | 43 | public function listByManufacturerAction(Manufacturer $manufacturer) { 44 | $this->view->assign('products', $this->productRepository->findByManufacturer($manufacturer)); 45 | } 46 | 47 | public function initializeCreateAction() { 48 | $propertyMappingConfiguration = $this->arguments['product']->getPropertyMappingConfiguration(); 49 | $propertyMappingConfiguration->allowAllProperties(); 50 | $propertyMappingConfiguration->forProperty('manufacturer')->allowAllProperties(); 51 | } 52 | 53 | public function showAction(Product $product) { 54 | $this->view->assign('product', $product); 55 | } 56 | 57 | public function createAction(ProductDto $product) { 58 | $realProduct = $this->productMapper->createProduct($product); 59 | $this->productRepository->add($realProduct); 60 | 61 | $this->view->assign('product', $realProduct); 62 | $this->response->setStatus(201); 63 | } 64 | 65 | public function initializeUpdateAction() { 66 | $propertyMappingConfiguration = $this->arguments['product']->getPropertyMappingConfiguration(); 67 | $propertyMappingConfiguration->allowAllProperties(); 68 | $propertyMappingConfiguration->skipUnknownProperties(TRUE); 69 | $propertyMappingConfiguration->forProperty('manufacturer')->allowAllProperties(); 70 | $propertyMappingConfiguration->forProperty('manufacturer')->skipUnknownProperties(); 71 | } 72 | 73 | /** 74 | * @param string $productIdentifier 75 | * @param ProductDto $product 76 | * @throws \TYPO3\Flow\Persistence\Exception\IllegalObjectTypeException 77 | */ 78 | public function updateAction($productIdentifier, ProductDto $product) { 79 | $realProduct = $this->productMapper->updateProduct($productIdentifier, $product); 80 | $this->productRepository->update($realProduct); 81 | 82 | $this->view->assign('product', $realProduct); 83 | } 84 | 85 | public function deleteAction(Product $product) { 86 | $this->productRepository->remove($product); 87 | $this->view->assign('product', $product); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /.idea/restapi-example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------