├── .gitignore ├── tests ├── fixtures │ ├── BasketUpdateRequest.json │ ├── BasketAddItemRequest.json │ ├── WishlistAddItemRequest.json │ └── BrandGetResponse.json ├── RedirectTest.php ├── PromotionTest.php ├── SearchTest.php ├── AttributeTest.php ├── FilterTest.php ├── ShopConfigurationTest.php ├── MatchRedirectTest.php ├── CampaignTest.php ├── VariantTest.php ├── TypeaheadTest.php ├── NavigationTest.php ├── BrandTest.php ├── WishlistTest.php ├── BasketTest.php ├── CategoryTest.php ├── ProductTest.php └── BaseApiTestCase.php ├── phpstan.neon.dist ├── lib ├── Models │ ├── ApiCollection.php │ ├── ArrayCollection.php │ ├── Search.php │ ├── Navigation.php │ ├── Packages.php │ ├── Timestamp.php │ ├── DisplayData.php │ ├── FilterValues.php │ ├── BrandCustomData.php │ ├── BasketItemPromotion.php │ ├── ResponseCustomData.php │ ├── NavigationTreeResponse.php │ ├── SearchV2ResolveResponse.php │ ├── FilterCollection.php │ ├── ProductName.php │ ├── CategoryCollection.php │ ├── OffsetPagination.php │ ├── Vat.php │ ├── SearchResolveResponse.php │ ├── Deliverable.php │ ├── DeliveryDate.php │ ├── NavigationItemExtraFilter.php │ ├── SearchV2ProductSuggestion.php │ ├── BasketItemPrice.php │ ├── DefiningAttribute.php │ ├── NavigationTreeCollection.php │ ├── ShopProperties.php │ ├── Tax.php │ ├── SubsequentDelivery.php │ ├── BooleanFilterValue.php │ ├── BuyXGetYEffect.php │ ├── CategoryProperty.php │ ├── MatchRedirectBody.php │ ├── IdentifierFilterValue.php │ ├── AppliedReductionAmount.php │ ├── AutomaticDiscountEffect.php │ ├── Condition.php │ ├── PromotionsResponse.php │ ├── SearchV2AttributeFilter.php │ ├── AttributeValue.php │ ├── RangeFilterValue.php │ ├── Tier.php │ ├── DeliveryForecast.php │ ├── RedirectsResponse.php │ ├── Merchant.php │ ├── SearchV2SuggestionsResponse.php │ ├── AdvancedAttributeGroupSet.php │ ├── SearchV2CategorySuggestion.php │ ├── SearchV2ProductResponse.php │ ├── AppliedReduction.php │ ├── SearchV2CategoryResponse.php │ ├── NavigationTree.php │ ├── ResolveMatch.php │ ├── TypeaheadResponse.php │ ├── Wishlist.php │ ├── AttributeFilterValue.php │ ├── Image.php │ ├── Package.php │ ├── ProductSuggestion.php │ ├── BrandsResponse.php │ ├── Cost.php │ ├── PriceRange.php │ ├── ProductsResponse.php │ ├── ReductionRange.php │ ├── AdvancedAttribute.php │ ├── Match.php │ ├── Stock.php │ ├── MatchRedirect.php │ ├── Basket.php │ ├── Pagination.php │ ├── TypeaheadProductSuggestion.php │ ├── VariantsResponse.php │ ├── ApplicablePromotion.php │ ├── BaseCategory.php │ ├── BrandOrCategorySuggestion.php │ ├── CampaignsResponse.php │ ├── CustomData.php │ ├── Campaign.php │ ├── TypeaheadBrandOrCategorySuggestion.php │ ├── Filter.php │ ├── Typeahead.php │ ├── BasketItemDisplayData.php │ ├── TypeaheadSuggestion.php │ ├── ProductCategory.php │ ├── CreateWishlistBody.php │ ├── Identifier.php │ ├── Redirect.php │ ├── DisplayDataValue.php │ ├── CreateBasketItemBody.php │ ├── LowestPriorPrice.php │ ├── NavigationItem.php │ ├── ShopConfiguration.php │ ├── Price.php │ ├── SearchSuggestions.php │ ├── UpdateBasketItemBody.php │ ├── WishlistItem.php │ ├── TypeaheadRequestBody.php │ ├── Attribute.php │ ├── Promotion.php │ ├── TypeaheadBody.php │ ├── ItemGroup.php │ ├── CreateBasketBody.php │ ├── Variant.php │ ├── Brand.php │ ├── BasketItem.php │ ├── Category.php │ ├── Product.php │ └── ApiObject.php ├── Exceptions │ ├── InvalidArgumentException.php │ ├── ApiError.php │ └── ApiErrorException.php ├── Serializers │ ├── AttributeAdapter.php │ └── ModelSerializer.php ├── Services │ ├── AttributeService.php │ ├── FilterService.php │ ├── RedirectService.php │ ├── PromotionService.php │ ├── SearchService.php │ ├── VariantService.php │ ├── MatchRedirectService.php │ ├── ShopConfigurationService.php │ ├── AbstractServiceFactory.php │ ├── TypeaheadService.php │ ├── NavigationService.php │ ├── CampaignService.php │ ├── BrandService.php │ ├── ProductService.php │ ├── ServiceFactory.php │ ├── WishlistService.php │ ├── CategoryService.php │ ├── BasketService.php │ └── AbstractService.php ├── StorefrontClient.php └── AbstractApi.php ├── phpunit.xml.dist ├── composer.json ├── .php_cs.dist ├── .gitlab-ci.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .api 2 | -------------------------------------------------------------------------------- /tests/fixtures/BasketUpdateRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "quantity": 2 3 | } -------------------------------------------------------------------------------- /tests/fixtures/BasketAddItemRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "variantId": 5, 3 | "quantity": 1 4 | } -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 4 3 | paths: 4 | - lib 5 | - tests -------------------------------------------------------------------------------- /tests/fixtures/WishlistAddItemRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "productId": 7, 3 | "quantity": 1 4 | } -------------------------------------------------------------------------------- /lib/Models/ApiCollection.php: -------------------------------------------------------------------------------- 1 | entities; 16 | } 17 | } -------------------------------------------------------------------------------- /lib/Serializers/AttributeAdapter.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | 9 | lib 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/Models/DisplayData.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Filter::class, 12 | ]; 13 | 14 | /** 15 | * @return \Scayle\StorefrontApi\Models\Filter[] 16 | */ 17 | public function getEntities() 18 | { 19 | return $this->entities; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/Models/ProductName.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Category::class, 12 | ]; 13 | 14 | /** 15 | * @return \Scayle\StorefrontApi\Models\Category[] 16 | */ 17 | public function getEntities() 18 | { 19 | return $this->entities; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/Models/OffsetPagination.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\NavigationTree::class, 12 | ]; 13 | 14 | /** 15 | * @return \Scayle\StorefrontApi\Models\NavigationTree[] 16 | */ 17 | public function getEntities() 18 | { 19 | return $this->entities; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/Models/ShopProperties.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Vat::class, 16 | ]; 17 | 18 | protected $collectionClassMap = [ 19 | ]; 20 | 21 | protected $collection2dClassMap = [ 22 | ]; 23 | 24 | protected $polymorphic = [ 25 | ]; 26 | 27 | protected $polymorphicCollections = [ 28 | ]; 29 | } -------------------------------------------------------------------------------- /lib/Models/SubsequentDelivery.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\WishlistItem::class, 20 | ]; 21 | 22 | protected $collection2dClassMap = [ 23 | ]; 24 | 25 | protected $polymorphic = [ 26 | ]; 27 | 28 | protected $polymorphicCollections = [ 29 | ]; 30 | } -------------------------------------------------------------------------------- /lib/Models/AttributeFilterValue.php: -------------------------------------------------------------------------------- 1 | api->redirects->Get( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('RedirectGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\RedirectsResponse::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/Models/Image.php: -------------------------------------------------------------------------------- 1 | $attributes 7 | * @property string $hash 8 | */ 9 | class Image extends ApiObject 10 | { 11 | protected $defaultValues = [ 12 | 13 | ]; 14 | 15 | protected $classMap = [ 16 | ]; 17 | 18 | protected $collectionClassMap = [ 19 | 'attributes' => \Scayle\StorefrontApi\Models\Attribute::class, 20 | ]; 21 | 22 | protected $collection2dClassMap = [ 23 | ]; 24 | 25 | protected $polymorphic = [ 26 | ]; 27 | 28 | protected $polymorphicCollections = [ 29 | ]; 30 | } -------------------------------------------------------------------------------- /lib/Models/Package.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Product::class, 17 | ]; 18 | 19 | protected $collectionClassMap = [ 20 | ]; 21 | 22 | protected $collection2dClassMap = [ 23 | ]; 24 | 25 | protected $polymorphic = [ 26 | ]; 27 | 28 | protected $polymorphicCollections = [ 29 | ]; 30 | } -------------------------------------------------------------------------------- /lib/Serializers/ModelSerializer.php: -------------------------------------------------------------------------------- 1 | AttributeAdapter::class, 9 | ]; 10 | 11 | public function parse($class, $attributes) 12 | { 13 | foreach ($this->adapters as $adapterClass => $adapter) { 14 | if ($adapterClass !== $class) { 15 | continue; 16 | } 17 | return (new $adapter())->deserialize($attributes); 18 | } 19 | 20 | return new $class($attributes); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/PromotionTest.php: -------------------------------------------------------------------------------- 1 | api->promotions->Get( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('PromotionGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\PromotionsResponse::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/Models/BrandsResponse.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Brand::class, 20 | ]; 21 | 22 | protected $collection2dClassMap = [ 23 | ]; 24 | 25 | protected $polymorphic = [ 26 | ]; 27 | 28 | protected $polymorphicCollections = [ 29 | ]; 30 | } -------------------------------------------------------------------------------- /tests/SearchTest.php: -------------------------------------------------------------------------------- 1 | api->searches->Suggestions('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('SearchSuggestionsResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\SearchSuggestions::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/Models/Cost.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Price::class, 17 | 'min' => \Scayle\StorefrontApi\Models\Price::class, 18 | ]; 19 | 20 | protected $collectionClassMap = [ 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/ProductsResponse.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Product::class, 20 | ]; 21 | 22 | protected $collection2dClassMap = [ 23 | ]; 24 | 25 | protected $polymorphic = [ 26 | ]; 27 | 28 | protected $polymorphicCollections = [ 29 | ]; 30 | } -------------------------------------------------------------------------------- /lib/Models/ReductionRange.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Price::class, 17 | 'min' => \Scayle\StorefrontApi\Models\Price::class, 18 | ]; 19 | 20 | protected $collectionClassMap = [ 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/AdvancedAttribute.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\ProductSuggestion::class, 18 | ]; 19 | 20 | protected $collectionClassMap = [ 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/VariantsResponse.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Pagination::class, 17 | ]; 18 | 19 | protected $collectionClassMap = [ 20 | 'entities' => \Scayle\StorefrontApi\Models\Variant::class, 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scayle/storefront-api", 3 | "description": "SCAYLE Storefront API SDK", 4 | "type": "library", 5 | "require": { 6 | "ext-json": "*", 7 | "psr/http-client": "^1.0", 8 | "guzzlehttp/guzzle": "^7.0" 9 | }, 10 | "require-dev": { 11 | "phpunit/phpunit": "^9.3", 12 | "friendsofphp/php-cs-fixer": "^3.13", 13 | "phpstan/phpstan": "^1.9" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Scayle\\StorefrontApi\\": "lib/" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Scayle\\StorefrontApi\\": [ 23 | "tests/" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/Models/ApplicablePromotion.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Pagination::class, 17 | ]; 18 | 19 | protected $collectionClassMap = [ 20 | 'entities' => \Scayle\StorefrontApi\Models\Campaign::class, 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/CustomData.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\BrandOrCategorySuggestion::class, 18 | ]; 19 | 20 | protected $collectionClassMap = [ 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/Filter.php: -------------------------------------------------------------------------------- 1 | request('get', $this->resolvePath('attributes/%s', $groupName), $combinedOptions, \Scayle\StorefrontApi\Models\Attribute::class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/Models/Typeahead.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\TypeaheadSuggestion::class, 17 | ]; 18 | 19 | protected $collectionClassMap = [ 20 | 'suggestions' => \Scayle\StorefrontApi\Models\TypeaheadSuggestion::class, 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /lib/Models/BasketItemDisplayData.php: -------------------------------------------------------------------------------- 1 | request('get', 'filters', $combinedOptions, \Scayle\StorefrontApi\Models\FilterCollection::class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/AttributeTest.php: -------------------------------------------------------------------------------- 1 | api->attributes->GetByKey('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('AttributeGetByKeyResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Attribute::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'values', \Scayle\StorefrontApi\Models\AttributeValue::class); 18 | 19 | 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /lib/Services/RedirectService.php: -------------------------------------------------------------------------------- 1 | request('get', 'redirects', $combinedOptions, \Scayle\StorefrontApi\Models\RedirectsResponse::class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/Services/PromotionService.php: -------------------------------------------------------------------------------- 1 | request('get', 'promotions', $combinedOptions, \Scayle\StorefrontApi\Models\PromotionsResponse::class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/Models/TypeaheadSuggestion.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\BrandOrCategorySuggestion::class, 18 | 'productSuggestion' => \Scayle\StorefrontApi\Models\ProductSuggestion::class, 19 | ]; 20 | 21 | protected $collectionClassMap = [ 22 | ]; 23 | 24 | protected $collection2dClassMap = [ 25 | ]; 26 | 27 | protected $polymorphic = [ 28 | ]; 29 | 30 | protected $polymorphicCollections = [ 31 | ]; 32 | } -------------------------------------------------------------------------------- /tests/FilterTest.php: -------------------------------------------------------------------------------- 1 | api->filters->Get( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('FilterGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\FilterCollection::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | foreach ($responseEntity->getEntities() as $collectionEntity) { 20 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Filter::class, $collectionEntity); 21 | 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/Models/ProductCategory.php: -------------------------------------------------------------------------------- 1 | $customData 9 | * @property int $quantity The quantity for the variant. 10 | */ 11 | class CreateWishlistBody extends ApiObject 12 | { 13 | protected $defaultValues = [ 14 | 'quantity' => '1', 15 | ]; 16 | 17 | protected $classMap = [ 18 | ]; 19 | 20 | protected $collectionClassMap = [ 21 | ]; 22 | 23 | protected $collection2dClassMap = [ 24 | ]; 25 | 26 | protected $polymorphic = [ 27 | ]; 28 | 29 | protected $polymorphicCollections = [ 30 | ]; 31 | } -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | in(__DIR__ . '/lib') 5 | ->in(__DIR__ . '/tests') 6 | ->name('*.php'); 7 | 8 | return PhpCsFixer\Config::create() 9 | ->setRiskyAllowed(true) 10 | ->setFinder($finder) 11 | ->setRules([ 12 | '@PSR2' => true, 13 | '@PhpCsFixer' => true, 14 | '@PhpCsFixer:risky' => true, 15 | '@PHP56Migration:risky' => true, 16 | '@PHPUnit57Migration:risky' => true, 17 | 'fopen_flags' => true, 18 | 'linebreak_after_opening_tag' => true, 19 | 'native_function_invocation' => true, 20 | 'concat_space' => ['spacing' => 'one'], 21 | 'ordered_class_elements' => false, 22 | 'phpdoc_align' => false, 23 | 'self_accessor' => false, 24 | 'php_unit_test_class_requires_covers' => false 25 | ]); -------------------------------------------------------------------------------- /lib/Models/Identifier.php: -------------------------------------------------------------------------------- 1 | identifier = $identifier; 15 | } 16 | 17 | /** 18 | * @param int $id 19 | * 20 | * @return Identifier 21 | */ 22 | public static function fromId($id) 23 | { 24 | return new self('' . $id); 25 | } 26 | 27 | /** 28 | * @param string $referenceKey 29 | * 30 | * @return Identifier 31 | */ 32 | public static function fromKey($referenceKey) 33 | { 34 | return new self('key=' . $referenceKey); 35 | } 36 | 37 | public function __toString() 38 | { 39 | return $this->identifier; 40 | } 41 | } -------------------------------------------------------------------------------- /lib/Models/Redirect.php: -------------------------------------------------------------------------------- 1 | request('get', 'search/suggestions', $combinedOptions, \Scayle\StorefrontApi\Models\SearchSuggestions::class); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/Models/LowestPriorPrice.php: -------------------------------------------------------------------------------- 1 | request('get', 'variants', $combinedOptions, \Scayle\StorefrontApi\Models\VariantsResponse::class); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/Models/ShopConfiguration.php: -------------------------------------------------------------------------------- 1 | $shopCustomData 9 | * @property ShopProperties[] $properties 10 | * @property array $customData 11 | * @property string $country 12 | */ 13 | class ShopConfiguration extends ApiObject 14 | { 15 | protected $defaultValues = [ 16 | 17 | ]; 18 | 19 | protected $classMap = [ 20 | 'customData' => \Scayle\StorefrontApi\Models\CustomData::class, 21 | ]; 22 | 23 | protected $collectionClassMap = [ 24 | 'properties' => \Scayle\StorefrontApi\Models\ShopProperties::class, 25 | ]; 26 | 27 | protected $collection2dClassMap = [ 28 | ]; 29 | 30 | protected $polymorphic = [ 31 | ]; 32 | 33 | protected $polymorphicCollections = [ 34 | ]; 35 | } -------------------------------------------------------------------------------- /lib/Services/MatchRedirectService.php: -------------------------------------------------------------------------------- 1 | request('post', 'redirects', $combinedOptions, \Scayle\StorefrontApi\Models\MatchRedirect::class, $model); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /lib/Services/ShopConfigurationService.php: -------------------------------------------------------------------------------- 1 | request('get', 'shop-configuration', $combinedOptions, \Scayle\StorefrontApi\Models\ShopConfiguration::class); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /lib/Models/Price.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Tax::class, 22 | ]; 23 | 24 | protected $collectionClassMap = [ 25 | 'appliedReductions' => \Scayle\StorefrontApi\Models\AppliedReduction::class, 26 | ]; 27 | 28 | protected $collection2dClassMap = [ 29 | ]; 30 | 31 | protected $polymorphic = [ 32 | ]; 33 | 34 | protected $polymorphicCollections = [ 35 | ]; 36 | } -------------------------------------------------------------------------------- /tests/ShopConfigurationTest.php: -------------------------------------------------------------------------------- 1 | api->shopConfigurations->Get('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('ShopConfigurationGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\ShopConfiguration::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'customData', \Scayle\StorefrontApi\Models\CustomData::class); 18 | $this->assertPropertyHasTheCorrectType($responseEntity, 'properties', \Scayle\StorefrontApi\Models\ShopProperties::class); 19 | 20 | 21 | 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /lib/Services/AbstractServiceFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | protected $classMap = []; 13 | 14 | /** 15 | * @var array 16 | */ 17 | private $services; 18 | 19 | /** 20 | * @var AbstractApi 21 | */ 22 | private $client; 23 | 24 | /** 25 | * @param AbstractApi $client 26 | */ 27 | public function __construct($client) 28 | { 29 | $this->client = $client; 30 | $this->services = []; 31 | } 32 | 33 | public function get($name) 34 | { 35 | if (!array_key_exists($name, $this->services)) { 36 | $this->services[$name] = new $this->classMap[$name]($this->client); 37 | } 38 | 39 | return $this->services[$name]; 40 | } 41 | } -------------------------------------------------------------------------------- /lib/Models/SearchSuggestions.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\AttributeValue::class, 22 | 'categories' => \Scayle\StorefrontApi\Models\Category::class, 23 | 'productNames' => \Scayle\StorefrontApi\Models\ProductName::class, 24 | 'products' => \Scayle\StorefrontApi\Models\Product::class, 25 | ]; 26 | 27 | protected $collection2dClassMap = [ 28 | ]; 29 | 30 | protected $polymorphic = [ 31 | ]; 32 | 33 | protected $polymorphicCollections = [ 34 | ]; 35 | } -------------------------------------------------------------------------------- /lib/Services/TypeaheadService.php: -------------------------------------------------------------------------------- 1 | request('post', 'typeahead', $combinedOptions, \Scayle\StorefrontApi\Models\Typeahead::class, $model); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/MatchRedirectTest.php: -------------------------------------------------------------------------------- 1 | loadFixture('MatchRedirectMatchRequest.json'); 12 | 13 | $requestEntity = new \Scayle\StorefrontApi\Models\MatchRedirectBody($expectedRequestJson); 14 | $this->assertJsonStringEqualsJsonString(json_encode($expectedRequestJson), $requestEntity->toJson()); 15 | 16 | $responseEntity = $this->api->matchRedirects->Match($requestEntity, []); 17 | 18 | $expectedResponseJson = $this->loadFixture('MatchRedirectMatchResponse.json'); 19 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\MatchRedirect::class, $responseEntity); 20 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 21 | 22 | 23 | 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /lib/Models/UpdateBasketItemBody.php: -------------------------------------------------------------------------------- 1 | errorKey = $errorKey; 25 | $this->message = $message; 26 | $this->context = $context; 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function getErrorKey() 33 | { 34 | return $this->errorKey; 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getMessage() 41 | { 42 | return $this->message; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | public function getContext() 49 | { 50 | return $this->context; 51 | } 52 | } -------------------------------------------------------------------------------- /lib/Models/WishlistItem.php: -------------------------------------------------------------------------------- 1 | $customData 16 | */ 17 | class WishlistItem extends ApiObject 18 | { 19 | protected $defaultValues = [ 20 | 21 | ]; 22 | 23 | protected $classMap = [ 24 | 'product' => \Scayle\StorefrontApi\Models\Product::class, 25 | 'variant' => \Scayle\StorefrontApi\Models\Variant::class, 26 | 'customData' => \Scayle\StorefrontApi\Models\CustomData::class, 27 | ]; 28 | 29 | protected $collectionClassMap = [ 30 | ]; 31 | 32 | protected $collection2dClassMap = [ 33 | ]; 34 | 35 | protected $polymorphic = [ 36 | ]; 37 | 38 | protected $polymorphicCollections = [ 39 | ]; 40 | } -------------------------------------------------------------------------------- /lib/Models/TypeaheadRequestBody.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\AttributeValue::class, 24 | ]; 25 | 26 | protected $collection2dClassMap = [ 27 | ]; 28 | 29 | protected $polymorphic = [ 30 | ]; 31 | 32 | protected $polymorphicCollections = [ 33 | ]; 34 | } -------------------------------------------------------------------------------- /tests/CampaignTest.php: -------------------------------------------------------------------------------- 1 | api->campaigns->GetAll( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('CampaignGetAllResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\CampaignsResponse::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | 20 | } 21 | 22 | public function testGetById() 23 | { 24 | $responseEntity = $this->api->campaigns->GetById('1', []); 25 | 26 | $expectedResponseJson = $this->loadFixture('CampaignGetByIdResponse.json'); 27 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Campaign::class, $responseEntity); 28 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 29 | 30 | 31 | 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/Models/Promotion.php: -------------------------------------------------------------------------------- 1 | api->variants->GetByIds('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('VariantGetByIdsResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\VariantsResponse::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'attributes', \Scayle\StorefrontApi\Models\Attribute::class); 18 | $this->assertPropertyHasTheCorrectType($responseEntity, 'advancedAttributes', \Scayle\StorefrontApi\Models\AdvancedAttribute::class); 19 | $this->assertPropertyHasTheCorrectType($responseEntity, 'price', \Scayle\StorefrontApi\Models\Price::class); 20 | $this->assertPropertyHasTheCorrectType($responseEntity, 'stock', \Scayle\StorefrontApi\Models\Stock::class); 21 | 22 | 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /tests/fixtures/BrandGetResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": [ 3 | { 4 | "id": 21, 5 | "name": "About You", 6 | "slug": "aboutyou", 7 | "group": "default", 8 | "isActive": true, 9 | "logoHash": "01dfae6e5d4d90d9892622325959afbe", 10 | "createdAt": "2019-04-17 16:05:36", 11 | "updatedAt": "2019-04-17 16:05:36", 12 | "indexedAt": "2023-04-26 16:05:36" 13 | }, 14 | { 15 | "id": 22, 16 | "name": "Edited", 17 | "slug": "edited", 18 | "group": "default", 19 | "externalReference": "0815", 20 | "isActive": true, 21 | "logoHash": "8743b52063cd84097a65d1633f5c74f5", 22 | "createdAt": "2019-04-17 16:05:36", 23 | "updatedAt": "2019-04-17 16:05:36", 24 | "indexedAt": "2023-04-26 16:05:36" 25 | } 26 | ], 27 | "pagination": { 28 | "current": 2, 29 | "first": 1, 30 | "last": 49, 31 | "next": 2, 32 | "page": 1, 33 | "perPage": 2, 34 | "prev": 1, 35 | "total": 4879 36 | } 37 | } -------------------------------------------------------------------------------- /lib/Models/ItemGroup.php: -------------------------------------------------------------------------------- 1 | request('get', 'navigation/trees', $combinedOptions, \Scayle\StorefrontApi\Models\NavigationTreeCollection::class); 25 | } 26 | 27 | /** 28 | * Description 29 | * 30 | * @param int $navigationTreeId 31 | * 32 | * @return \Scayle\StorefrontApi\Models\NavigationTree 33 | * @throws ClientExceptionInterface 34 | * @throws ApiErrorException 35 | */ 36 | public function getById($navigationTreeId) 37 | { 38 | $combinedOptions = []; 39 | 40 | return $this->request('get', $this->resolvePath('navigation/trees/%s', $navigationTreeId), $combinedOptions, \Scayle\StorefrontApi\Models\NavigationTree::class); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /lib/Services/CampaignService.php: -------------------------------------------------------------------------------- 1 | request('get', 'campaigns', $combinedOptions, \Scayle\StorefrontApi\Models\CampaignsResponse::class); 26 | } 27 | 28 | /** 29 | * Description 30 | * 31 | * @param int $campaignId 32 | * 33 | * @return \Scayle\StorefrontApi\Models\Campaign 34 | * @throws ClientExceptionInterface 35 | * @throws ApiErrorException 36 | */ 37 | public function getById($campaignId) 38 | { 39 | $combinedOptions = []; 40 | 41 | return $this->request('get', $this->resolvePath('campaigns/%s', $campaignId), $combinedOptions, \Scayle\StorefrontApi\Models\Campaign::class); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/TypeaheadTest.php: -------------------------------------------------------------------------------- 1 | loadFixture('TypeaheadPostSuggestionsRequest.json'); 12 | 13 | $requestEntity = new \Scayle\StorefrontApi\Models\TypeaheadBody($expectedRequestJson); 14 | $this->assertJsonStringEqualsJsonString(json_encode($expectedRequestJson), $requestEntity->toJson()); 15 | 16 | $responseEntity = $this->api->typeaheads->PostSuggestions('1', $requestEntity, []); 17 | 18 | $expectedResponseJson = $this->loadFixture('TypeaheadPostSuggestionsResponse.json'); 19 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Typeahead::class, $responseEntity); 20 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 21 | 22 | $this->assertPropertyHasTheCorrectType($responseEntity, 'suggestions', \Scayle\StorefrontApi\Models\TypeaheadSuggestion::class); 23 | $this->assertPropertyHasTheCorrectType($responseEntity, 'topMatch', \Scayle\StorefrontApi\Models\TypeaheadSuggestion::class); 24 | 25 | 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/Models/CreateBasketBody.php: -------------------------------------------------------------------------------- 1 | api->navigations->GetAll( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('NavigationGetAllResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\NavigationTreeCollection::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | foreach ($responseEntity->getEntities() as $collectionEntity) { 20 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\NavigationTree::class, $collectionEntity); 21 | 22 | } 23 | } 24 | 25 | public function testGetById() 26 | { 27 | $responseEntity = $this->api->navigations->GetById('1', []); 28 | 29 | $expectedResponseJson = $this->loadFixture('NavigationGetByIdResponse.json'); 30 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\NavigationTree::class, $responseEntity); 31 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 32 | 33 | 34 | 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /lib/Services/BrandService.php: -------------------------------------------------------------------------------- 1 | request('get', 'brands', $combinedOptions, \Scayle\StorefrontApi\Models\BrandsResponse::class); 26 | } 27 | 28 | /** 29 | * Description 30 | * 31 | * @param int $brandId 32 | * @param array $options additional options like limit or filters 33 | * 34 | * @return \Scayle\StorefrontApi\Models\BrandsResponse 35 | * @throws ClientExceptionInterface 36 | * @throws ApiErrorException 37 | */ 38 | public function getById($brandId, $options = []) 39 | { 40 | $combinedOptions = $options; 41 | 42 | return $this->request('get', $this->resolvePath('brands/%s', $brandId), $combinedOptions, \Scayle\StorefrontApi\Models\BrandsResponse::class); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/BrandTest.php: -------------------------------------------------------------------------------- 1 | api->brands->Get( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('BrandGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\BrandsResponse::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'customData', \Scayle\StorefrontApi\Models\CustomData::class); 18 | 19 | 20 | 21 | } 22 | 23 | public function testGetById() 24 | { 25 | $responseEntity = $this->api->brands->GetById('1', []); 26 | 27 | $expectedResponseJson = $this->loadFixture('BrandGetByIdResponse.json'); 28 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\BrandsResponse::class, $responseEntity); 29 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 30 | 31 | $this->assertPropertyHasTheCorrectType($responseEntity, 'customData', \Scayle\StorefrontApi\Models\CustomData::class); 32 | 33 | 34 | 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /lib/Services/ProductService.php: -------------------------------------------------------------------------------- 1 | request('get', $this->resolvePath('products/%s', $productId), $combinedOptions, \Scayle\StorefrontApi\Models\Product::class); 27 | } 28 | 29 | /** 30 | * Description 31 | * 32 | * @param array $options additional options like limit or filters 33 | * 34 | * @return \Scayle\StorefrontApi\Models\ProductsResponse 35 | * @throws ClientExceptionInterface 36 | * @throws ApiErrorException 37 | */ 38 | public function query($options = []) 39 | { 40 | $combinedOptions = $options; 41 | 42 | return $this->request('get', 'products', $combinedOptions, \Scayle\StorefrontApi\Models\ProductsResponse::class); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /lib/Models/Variant.php: -------------------------------------------------------------------------------- 1 | $advancedAttributes 8 | * @property string $appliedPricePromotionKey 9 | * @property array $attributes 10 | * @property LowestPriorPrice $lowestPriorPrice 11 | * @property Price $price 12 | * @property int $productId 13 | * @property string $referenceKey 14 | * @property Timestamp $firstLiveAt 15 | * @property Stock $stock 16 | * @property array $customData 17 | * @property Merchant $merchant 18 | * @property Timestamp $createdAt 19 | * @property Timestamp $updatedAt 20 | */ 21 | class Variant extends ApiObject 22 | { 23 | protected $defaultValues = [ 24 | 25 | ]; 26 | 27 | protected $classMap = [ 28 | 'price' => \Scayle\StorefrontApi\Models\Price::class, 29 | 'stock' => \Scayle\StorefrontApi\Models\Stock::class, 30 | ]; 31 | 32 | protected $collectionClassMap = [ 33 | 'attributes' => \Scayle\StorefrontApi\Models\Attribute::class, 34 | 'advancedAttributes' => \Scayle\StorefrontApi\Models\AdvancedAttribute::class, 35 | ]; 36 | 37 | protected $collection2dClassMap = [ 38 | ]; 39 | 40 | protected $polymorphic = [ 41 | ]; 42 | 43 | protected $polymorphicCollections = [ 44 | ]; 45 | } -------------------------------------------------------------------------------- /lib/Models/Brand.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\CustomData::class, 29 | ]; 30 | 31 | protected $collection2dClassMap = [ 32 | ]; 33 | 34 | protected $polymorphic = [ 35 | ]; 36 | 37 | protected $polymorphicCollections = [ 38 | ]; 39 | } -------------------------------------------------------------------------------- /lib/Services/ServiceFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected $classMap = [ 14 | 'baskets' => \Scayle\StorefrontApi\Services\BasketService::class, 15 | 'attributes' => \Scayle\StorefrontApi\Services\AttributeService::class, 16 | 'brands' => \Scayle\StorefrontApi\Services\BrandService::class, 17 | 'categories' => \Scayle\StorefrontApi\Services\CategoryService::class, 18 | 'filters' => \Scayle\StorefrontApi\Services\FilterService::class, 19 | 'typeahead' => \Scayle\StorefrontApi\Services\TypeaheadService::class, 20 | 'campaign' => \Scayle\StorefrontApi\Services\CampaignService::class, 21 | 'navigation' => \Scayle\StorefrontApi\Services\NavigationService::class, 22 | 'products' => \Scayle\StorefrontApi\Services\ProductService::class, 23 | 'shopConfigurations' => \Scayle\StorefrontApi\Services\ShopConfigurationService::class, 24 | 'variants' => \Scayle\StorefrontApi\Services\VariantService::class, 25 | 'wishlists' => \Scayle\StorefrontApi\Services\WishlistService::class, 26 | 'redirects' => \Scayle\StorefrontApi\Services\RedirectService::class, 27 | 'matchRedirects' => \Scayle\StorefrontApi\Services\MatchRedirectService::class, 28 | 'promotions' => \Scayle\StorefrontApi\Services\PromotionService::class, 29 | ]; 30 | } -------------------------------------------------------------------------------- /lib/Models/BasketItem.php: -------------------------------------------------------------------------------- 1 | serviceFactory === null) { 36 | $this->serviceFactory = new ServiceFactory($this); 37 | } 38 | 39 | return $this->serviceFactory->get($name); 40 | } 41 | } -------------------------------------------------------------------------------- /lib/Exceptions/ApiErrorException.php: -------------------------------------------------------------------------------- 1 | statusCode = $statusCode; 26 | $this->errors = $this->parseErrors($responseErrors); 27 | parent::__construct("Errors occured while handling the API request ", $statusCode); 28 | } 29 | 30 | /** 31 | * @return ApiError|null 32 | */ 33 | public function getFirstError() 34 | { 35 | return empty($this->errors) ? null : $this->errors[0]; 36 | } 37 | 38 | /** 39 | * @return ApiError[] 40 | */ 41 | public function getErrors() 42 | { 43 | return $this->errors; 44 | } 45 | 46 | /** 47 | * @return int 48 | */ 49 | public function getStatusCode() 50 | { 51 | return $this->statusCode; 52 | } 53 | 54 | /** 55 | * @param array $errors 56 | * @return ApiError[] 57 | */ 58 | private function parseErrors($errors) 59 | { 60 | $adminApiErrors = []; 61 | 62 | if (array_key_exists('errors', $errors)) { 63 | foreach ($errors['errors'] as $error) { 64 | $adminApiErrors[] = new ApiError($error['errorKey'], $error['message'], $error['context']); 65 | } 66 | } else { 67 | $code = $errors['code'] ?? $this->getStatusCode(); 68 | $adminApiErrors[] = new ApiError($code, $errors['message'] ?? '', ''); 69 | } 70 | 71 | 72 | return $adminApiErrors; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | workflow: 2 | rules: 3 | - if: $CI_PIPELINE_SOURCE == "pipeline" && $API_COMMIT_SHA 4 | - if: $CI_PIPELINE_SOURCE == "web" && $API_COMMIT_SHA 5 | 6 | default: 7 | image: docker:20 8 | services: 9 | - name: docker:dind 10 | command: ["--tls=false"] 11 | tags: 12 | - ay-shared-runner 13 | before_script: 14 | # Configure docker to also use our auth config for building the docker image 15 | # https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#option-3-use-docker_auth_config 16 | - mkdir -p $HOME/.docker 17 | - echo $DOCKER_AUTH_CONFIG > $HOME/.docker/config.json 18 | 19 | - apk add --no-cache curl git 20 | 21 | - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 22 | - docker pull $CI_REGISTRY/aboutyou/scayle/core-engine/storefront-unit/storefront-api/sdks/generator:latest 23 | 24 | stages: 25 | - build 26 | 27 | Build SDK: 28 | stage: build 29 | script: 30 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/aboutyou/scayle/core-engine/storefront-unit/storefront-api/storefront-api.git .api 31 | - cd .api 32 | - git checkout $API_COMMIT_SHA 33 | - cd .. 34 | 35 | - echo "Initializing Git" 36 | - git config user.name $SDK_AUTOMATION_USER 37 | - git config user.email $SDK_AUTOMATION_EMAIL 38 | - git remote set-url origin https://$SDK_AUTOMATION_USER:$SDK_AUTOMATION_TOKEN@gitlab.com/${CI_PROJECT_PATH}.git 39 | 40 | - git checkout -b chore/update-sdk-$API_COMMIT_SHA 41 | 42 | - cp .api/http/docs/swagger.yaml swagger.yaml 43 | - docker run 44 | --rm 45 | -v "$(pwd)":/gen/out/php 46 | -v "$(pwd)/swagger.yaml":/gen/swagger.yaml 47 | $CI_REGISTRY/aboutyou/scayle/core-engine/storefront-unit/storefront-api/sdks/generator:latest 48 | php ./src/CodeGen.php generate php-storefront swagger.yaml 49 | 50 | - git add . 51 | - 'git commit -m "chore: Upgrade to $API_COMMIT_SHA"' 52 | - git push -o merge_request.create --set-upstream origin chore/update-sdk-$API_COMMIT_SHA 53 | -------------------------------------------------------------------------------- /lib/Models/Category.php: -------------------------------------------------------------------------------- 1 | \Scayle\StorefrontApi\Models\Category::class, 32 | ]; 33 | 34 | protected $collectionClassMap = [ 35 | 'children' => \Scayle\StorefrontApi\Models\Category::class, 36 | ]; 37 | 38 | protected $collection2dClassMap = [ 39 | ]; 40 | 41 | protected $polymorphic = [ 42 | ]; 43 | 44 | protected $polymorphicCollections = [ 45 | ]; 46 | } -------------------------------------------------------------------------------- /lib/Services/WishlistService.php: -------------------------------------------------------------------------------- 1 | request('post', $this->resolvePath('wishlists/%s/items', $wishlistId), $combinedOptions, \Scayle\StorefrontApi\Models\Wishlist::class, $model); 28 | } 29 | 30 | /** 31 | * Description 32 | * 33 | * @param string $wishlistId 34 | * @param array $options additional options like limit or filters 35 | * 36 | * @return \Scayle\StorefrontApi\Models\Wishlist 37 | * @throws ClientExceptionInterface 38 | * @throws ApiErrorException 39 | */ 40 | public function get($wishlistId, $options = []) 41 | { 42 | $combinedOptions = $options; 43 | 44 | return $this->request('get', $this->resolvePath('wishlists/%s', $wishlistId), $combinedOptions, \Scayle\StorefrontApi\Models\Wishlist::class); 45 | } 46 | 47 | /** 48 | * Description 49 | * 50 | * @param string $wishlistId 51 | * @param string $itemKey 52 | * @param array $options additional options like limit or filters 53 | * 54 | * @return \Scayle\StorefrontApi\Models\Wishlist 55 | * @throws ClientExceptionInterface 56 | * @throws ApiErrorException 57 | */ 58 | public function remove($wishlistId, $itemKey, $options = []) 59 | { 60 | $combinedOptions = $options; 61 | 62 | return $this->request('delete', $this->resolvePath('wishlists/%s/items/%s', $wishlistId, $itemKey), $combinedOptions, \Scayle\StorefrontApi\Models\Wishlist::class); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /tests/WishlistTest.php: -------------------------------------------------------------------------------- 1 | loadFixture('WishlistAddItemRequest.json'); 12 | 13 | $requestEntity = new \Scayle\StorefrontApi\Models\CreateWishlistBody($expectedRequestJson); 14 | $this->assertJsonStringEqualsJsonString(json_encode($expectedRequestJson), $requestEntity->toJson()); 15 | 16 | $responseEntity = $this->api->wishlists->AddItem('1', $requestEntity, []); 17 | 18 | $expectedResponseJson = $this->loadFixture('WishlistAddItemResponse.json'); 19 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Wishlist::class, $responseEntity); 20 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 21 | 22 | $this->assertPropertyHasTheCorrectType($responseEntity, 'items', \Scayle\StorefrontApi\Models\WishlistItem::class); 23 | 24 | 25 | 26 | } 27 | 28 | public function testGet() 29 | { 30 | $responseEntity = $this->api->wishlists->Get('1', []); 31 | 32 | $expectedResponseJson = $this->loadFixture('WishlistGetResponse.json'); 33 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Wishlist::class, $responseEntity); 34 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 35 | 36 | $this->assertPropertyHasTheCorrectType($responseEntity, 'items', \Scayle\StorefrontApi\Models\WishlistItem::class); 37 | 38 | 39 | 40 | } 41 | 42 | public function testRemove() 43 | { 44 | $responseEntity = $this->api->wishlists->Remove('1', '1', []); 45 | 46 | $expectedResponseJson = $this->loadFixture('WishlistRemoveResponse.json'); 47 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Wishlist::class, $responseEntity); 48 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 49 | 50 | $this->assertPropertyHasTheCorrectType($responseEntity, 'items', \Scayle\StorefrontApi\Models\WishlistItem::class); 51 | 52 | 53 | 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![scayle-logo-cr](https://cdn-prod.scayle.com/public/media/general/SCAYLE-Commerce-Engine-header.png) 2 | 3 |

4 | SCAYLE Storefront API PHP SDK 5 |

6 | 7 |

8 | Documentation | 9 | Website 10 |

11 | 12 |

13 | The SCAYLE Storefront API PHP SDK streamlines interactions with Storefront REST APIs. 14 |

15 |

16 | SCAYLE's **Storefront API PHP SDK is released under the MIT license. 17 |

18 | 19 | ## Getting Started 20 | 21 | Visit the [Quickstart Guide](https://new.scayle.dev/en/developer-guide/introduction/apis#sdk-setup) to set up the Storefront API PHP SDK 22 | 23 | Visit the [Docs](https://new.scayle.dev) to learn more about our system requirements. 24 | 25 | ## Installation 26 | ```PHP 27 | # Install the PHP library with Composer 28 | `composer require scayle/storefront-api` 29 | 30 | # Initialise SDK 31 | use Scayle\StorefrontApi\StorefrontClient; 32 | 33 | $client = new StorefrontClient([ 34 | 'apiUrl' => 'https://{{tenant-space}}.storefront.api.scayle.cloud/v1/', 35 | 'shopId' => 1001, 36 | 'auth' => [ 37 | 'type' => 'token', 38 | 'token' => '{{Access-Token}}', 39 | ], 40 | ]); 41 | ``` 42 | ## What is Scayle ? 43 | 44 | [SCAYLE](https://scayle.com) is a full-featured e-commerce software solution that comes with flexible APIs. Within SCAYLE, you can manage all aspects of your shop, such as products, stocks, customers, and transactions. 45 | 46 | Learn more about Scayles’s architecture and commerce modules in the [Docs] (https://new.scayle.dev/en/user-guide). 47 | 48 | 49 | 50 | ## Community 51 | 52 | The community and core teams are available in [GitHub](https://github.com/scayle/), where you can ask for support, discuss roadmap, and share ideas. 53 | 54 | ## Other channels 55 | 56 | - [GitHub Issues](https://github.com/scayle/storefront-api-php-sdk/issues) 57 | - [LinkedIn](https://www.linkedin.com/company/scaylecommerce/) 58 | - [Jobs](https://careers.smartrecruiters.com/ABOUTYOUGmbH/scayle) 59 | - [AboutYou Tech Blog](https://aboutyou.tech/) 60 | 61 | ## License 62 | Licensed under the [MIT](https://opensource.org/license/mit/) 63 | -------------------------------------------------------------------------------- /lib/Models/Product.php: -------------------------------------------------------------------------------- 1 | $advancedAttributes 8 | * @property array $attributes 9 | * @property BaseCategory[] $baseCategories 10 | * @property ProductCategory[][] $categories 11 | * @property DefiningAttribute $definingAttributes 12 | * @property Image[] $images 13 | * @property ResponseCustomData $customData 14 | * @property bool $isActive Identifies whether a product is active or not 15 | * @property bool $isNew Identifies whether a product is new or not 16 | * @property bool $isSoldOut Identifies if a product is still available to sell 17 | * @property string $masterKey Identifies the master product which this product belongs 18 | * @property Timestamp $firstLiveAt 19 | * @property PriceRange $priceRange 20 | * @property ReductionRange $reductionRange 21 | * @property LowestPriorPrice $lowestPriorPrice 22 | * @property string $referenceKey 23 | * @property int[] $searchCategoryIds 24 | * @property Product[] $siblings list of Products 25 | * @property Variant[] $variants 26 | * @property Timestamp $createdAt 27 | * @property Timestamp $updatedAt 28 | * @property Timestamp $indexedAt 29 | */ 30 | class Product extends ApiObject 31 | { 32 | protected $defaultValues = [ 33 | 34 | ]; 35 | 36 | protected $classMap = [ 37 | 'definingAttributes' => \Scayle\StorefrontApi\Models\DefiningAttribute::class, 38 | 'priceRange' => \Scayle\StorefrontApi\Models\PriceRange::class, 39 | 'reductionRange' => \Scayle\StorefrontApi\Models\ReductionRange::class, 40 | 'lowestPriorPrice' => \Scayle\StorefrontApi\Models\LowestPriorPrice::class, 41 | ]; 42 | 43 | protected $collectionClassMap = [ 44 | 'attributes' => \Scayle\StorefrontApi\Models\Attribute::class, 45 | 'advancedAttributes' => \Scayle\StorefrontApi\Models\AdvancedAttribute::class, 46 | 'images' => \Scayle\StorefrontApi\Models\Image::class, 47 | 'siblings' => \Scayle\StorefrontApi\Models\Product::class, 48 | 'baseCategories' => \Scayle\StorefrontApi\Models\BaseCategory::class, 49 | 'variants' => \Scayle\StorefrontApi\Models\Variant::class, 50 | ]; 51 | 52 | protected $collection2dClassMap = [ 53 | 'categories' => \Scayle\StorefrontApi\Models\ProductCategory::class, 54 | ]; 55 | 56 | protected $polymorphic = [ 57 | ]; 58 | 59 | protected $polymorphicCollections = [ 60 | ]; 61 | } -------------------------------------------------------------------------------- /lib/Services/CategoryService.php: -------------------------------------------------------------------------------- 1 | request('get', 'categories', $combinedOptions, \Scayle\StorefrontApi\Models\CategoryCollection::class); 26 | } 27 | 28 | /** 29 | * Description 30 | * 31 | * @param array $categoryIds 32 | * @param array $options additional options like limit or filters 33 | * 34 | * @return \Scayle\StorefrontApi\Models\CategoryCollection 35 | * @throws ClientExceptionInterface 36 | * @throws ApiErrorException 37 | */ 38 | public function getByIds($categoryIds, $options = []) 39 | { 40 | $combinedOptions = $options; 41 | $combinedOptions["ids"] = implode(',', $categoryIds); 42 | 43 | return $this->request('get', 'categories', $combinedOptions, \Scayle\StorefrontApi\Models\CategoryCollection::class); 44 | } 45 | 46 | /** 47 | * Description 48 | * 49 | * @param int $categoryId 50 | * @param array $options additional options like limit or filters 51 | * 52 | * @return \Scayle\StorefrontApi\Models\Category 53 | * @throws ClientExceptionInterface 54 | * @throws ApiErrorException 55 | */ 56 | public function getById($categoryId, $options = []) 57 | { 58 | $combinedOptions = $options; 59 | 60 | return $this->request('get', $this->resolvePath('categories/%s', $categoryId), $combinedOptions, \Scayle\StorefrontApi\Models\Category::class); 61 | } 62 | 63 | /** 64 | * Description 65 | * 66 | * @param string $categoryPath 67 | * @param array $options additional options like limit or filters 68 | * 69 | * @return \Scayle\StorefrontApi\Models\Category 70 | * @throws ClientExceptionInterface 71 | * @throws ApiErrorException 72 | */ 73 | public function getByPath($categoryPath, $options = []) 74 | { 75 | $combinedOptions = $options; 76 | 77 | return $this->request('get', $this->resolvePath('categories/%s', $categoryPath), $combinedOptions, \Scayle\StorefrontApi\Models\Category::class); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /tests/BasketTest.php: -------------------------------------------------------------------------------- 1 | api->baskets->Get('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('BasketGetResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Basket::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | 18 | 19 | 20 | } 21 | 22 | public function testAddItem() 23 | { 24 | $expectedRequestJson = $this->loadFixture('BasketAddItemRequest.json'); 25 | 26 | $requestEntity = new \Scayle\StorefrontApi\Models\CreateBasketItemBody($expectedRequestJson); 27 | $this->assertJsonStringEqualsJsonString(json_encode($expectedRequestJson), $requestEntity->toJson()); 28 | 29 | $responseEntity = $this->api->baskets->AddItem('1', $requestEntity, []); 30 | 31 | $expectedResponseJson = $this->loadFixture('BasketAddItemResponse.json'); 32 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Basket::class, $responseEntity); 33 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 34 | 35 | 36 | 37 | 38 | } 39 | 40 | public function testRemove() 41 | { 42 | $responseEntity = $this->api->baskets->Remove('1', '1', []); 43 | 44 | $expectedResponseJson = $this->loadFixture('BasketRemoveResponse.json'); 45 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Basket::class, $responseEntity); 46 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 47 | 48 | 49 | 50 | 51 | } 52 | 53 | public function testUpdate() 54 | { 55 | $expectedRequestJson = $this->loadFixture('BasketUpdateRequest.json'); 56 | 57 | $requestEntity = new \Scayle\StorefrontApi\Models\UpdateBasketItemBody($expectedRequestJson); 58 | $this->assertJsonStringEqualsJsonString(json_encode($expectedRequestJson), $requestEntity->toJson()); 59 | 60 | $responseEntity = $this->api->baskets->Update('1', '1', $requestEntity, []); 61 | 62 | $expectedResponseJson = $this->loadFixture('BasketUpdateResponse.json'); 63 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Basket::class, $responseEntity); 64 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 65 | 66 | 67 | 68 | 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /lib/Services/BasketService.php: -------------------------------------------------------------------------------- 1 | request('get', $this->resolvePath('baskets/%s', $basketId), $combinedOptions, \Scayle\StorefrontApi\Models\Basket::class); 27 | } 28 | 29 | /** 30 | * Description 31 | * 32 | * @param string $basketId 33 | * @param \Scayle\StorefrontApi\Models\CreateBasketItemBody $model the model to create or update 34 | * @param array $options additional options like limit or filters 35 | * 36 | * @return \Scayle\StorefrontApi\Models\Basket 37 | * @throws ClientExceptionInterface 38 | * @throws ApiErrorException 39 | */ 40 | public function addItem($basketId, $model, $options = []) 41 | { 42 | $combinedOptions = $options; 43 | 44 | return $this->request('post', $this->resolvePath('baskets/%s/items', $basketId), $combinedOptions, \Scayle\StorefrontApi\Models\Basket::class, $model); 45 | } 46 | 47 | /** 48 | * Description 49 | * 50 | * @param string $basketId 51 | * @param string $itemKey 52 | * @param array $options additional options like limit or filters 53 | * 54 | * @return \Scayle\StorefrontApi\Models\Basket 55 | * @throws ClientExceptionInterface 56 | * @throws ApiErrorException 57 | */ 58 | public function remove($basketId, $itemKey, $options = []) 59 | { 60 | $combinedOptions = $options; 61 | 62 | return $this->request('delete', $this->resolvePath('baskets/%s/items/%s', $basketId, $itemKey), $combinedOptions, \Scayle\StorefrontApi\Models\Basket::class); 63 | } 64 | 65 | /** 66 | * Description 67 | * 68 | * @param string $basketId 69 | * @param string $itemKey 70 | * @param \Scayle\StorefrontApi\Models\UpdateBasketItemBody $model the model to create or update 71 | * @param array $options additional options like limit or filters 72 | * 73 | * @return \Scayle\StorefrontApi\Models\Basket 74 | * @throws ClientExceptionInterface 75 | * @throws ApiErrorException 76 | */ 77 | public function update($basketId, $itemKey, $model, $options = []) 78 | { 79 | $combinedOptions = $options; 80 | 81 | return $this->request('patch', $this->resolvePath('baskets/%s/items/%s', $basketId, $itemKey), $combinedOptions, \Scayle\StorefrontApi\Models\Basket::class, $model); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /lib/Services/AbstractService.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | } 25 | 26 | /** 27 | * @param string $method the http method 28 | * @param string $relativeUrl the relative url of endpoint 29 | * @param string|null $modelClass the classname of which the response gets transformed to 30 | * @param array $options array of additional options 31 | * @param ApiObject|null $body the request body object 32 | * 33 | * @return mixed|null 34 | * 35 | * @throws ClientExceptionInterface 36 | * @throws ApiErrorException 37 | */ 38 | protected function request($method, $relativeUrl, $options = [], $modelClass = null, $body = null) 39 | { 40 | try { 41 | if ($body instanceof ApiObject) { 42 | $body = $body->toJson(); 43 | } elseif ($body !== null) { 44 | $body = json_encode($body); 45 | } 46 | 47 | $response = $this->client->request($method, $relativeUrl, $options, $body); 48 | $statusCode = $response->getStatusCode(); 49 | 50 | $responseBody = $response->getBody()->getContents(); 51 | // Catching all NON 2xx status codes for further error processing 52 | if ($statusCode < 200 || $statusCode >= 300) { 53 | $responseJson = json_decode($responseBody, true); 54 | throw new ApiErrorException(is_null($responseJson) ? [] : $responseJson, $statusCode); 55 | } 56 | 57 | if ($responseBody && $modelClass && class_exists($modelClass)) { 58 | $responseJson = json_decode($responseBody, true); 59 | 60 | if (!is_subclass_of($modelClass, ApiCollection::class)) { 61 | return new $modelClass($responseJson); 62 | } 63 | 64 | return new $modelClass(['entities' => $responseJson]); 65 | } else { 66 | return json_decode($responseBody, true); 67 | } 68 | } catch (ClientExceptionInterface $e) { 69 | throw $e; 70 | } 71 | } 72 | 73 | /** 74 | * @param string $path 75 | * @param mixed ...$params 76 | * 77 | * @return string 78 | */ 79 | protected function resolvePath($path, ...$params) 80 | { 81 | return vsprintf($path, $params); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/CategoryTest.php: -------------------------------------------------------------------------------- 1 | api->categories->GetRoots( []); 12 | 13 | $expectedResponseJson = $this->loadFixture('CategoryGetRootsResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\CategoryCollection::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 18 | $this->assertPropertyHasTheCorrectType($responseEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 19 | 20 | 21 | foreach ($responseEntity->getEntities() as $collectionEntity) { 22 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Category::class, $collectionEntity); 23 | $this->assertPropertyHasTheCorrectType($collectionEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 24 | $this->assertPropertyHasTheCorrectType($collectionEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 25 | 26 | } 27 | } 28 | 29 | public function testGetByIds() 30 | { 31 | $responseEntity = $this->api->categories->GetByIds('1', []); 32 | 33 | $expectedResponseJson = $this->loadFixture('CategoryGetByIdsResponse.json'); 34 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\CategoryCollection::class, $responseEntity); 35 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 36 | 37 | $this->assertPropertyHasTheCorrectType($responseEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 38 | $this->assertPropertyHasTheCorrectType($responseEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 39 | 40 | 41 | foreach ($responseEntity->getEntities() as $collectionEntity) { 42 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Category::class, $collectionEntity); 43 | $this->assertPropertyHasTheCorrectType($collectionEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 44 | $this->assertPropertyHasTheCorrectType($collectionEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 45 | 46 | } 47 | } 48 | 49 | public function testGetById() 50 | { 51 | $responseEntity = $this->api->categories->GetById('1', []); 52 | 53 | $expectedResponseJson = $this->loadFixture('CategoryGetByIdResponse.json'); 54 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Category::class, $responseEntity); 55 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 56 | 57 | $this->assertPropertyHasTheCorrectType($responseEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 58 | $this->assertPropertyHasTheCorrectType($responseEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 59 | 60 | 61 | 62 | } 63 | 64 | public function testGetByPath() 65 | { 66 | $responseEntity = $this->api->categories->GetByPath('1', []); 67 | 68 | $expectedResponseJson = $this->loadFixture('CategoryGetByPathResponse.json'); 69 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Category::class, $responseEntity); 70 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 71 | 72 | $this->assertPropertyHasTheCorrectType($responseEntity, 'parent', \Scayle\StorefrontApi\Models\Category::class); 73 | $this->assertPropertyHasTheCorrectType($responseEntity, 'children', \Scayle\StorefrontApi\Models\Category::class); 74 | 75 | 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /tests/ProductTest.php: -------------------------------------------------------------------------------- 1 | api->products->GetById('1', []); 12 | 13 | $expectedResponseJson = $this->loadFixture('ProductGetByIdResponse.json'); 14 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\Product::class, $responseEntity); 15 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 16 | 17 | $this->assertPropertyHasTheCorrectType($responseEntity, 'attributes', \Scayle\StorefrontApi\Models\Attribute::class); 18 | $this->assertPropertyHasTheCorrectType($responseEntity, 'advancedAttributes', \Scayle\StorefrontApi\Models\AdvancedAttribute::class); 19 | $this->assertPropertyHasTheCorrectType($responseEntity, 'categories', \Scayle\StorefrontApi\Models\ProductCategory::class); 20 | $this->assertPropertyHasTheCorrectType($responseEntity, 'definingAttributes', \Scayle\StorefrontApi\Models\DefiningAttribute::class); 21 | $this->assertPropertyHasTheCorrectType($responseEntity, 'images', \Scayle\StorefrontApi\Models\Image::class); 22 | $this->assertPropertyHasTheCorrectType($responseEntity, 'priceRange', \Scayle\StorefrontApi\Models\PriceRange::class); 23 | $this->assertPropertyHasTheCorrectType($responseEntity, 'reductionRange', \Scayle\StorefrontApi\Models\ReductionRange::class); 24 | $this->assertPropertyHasTheCorrectType($responseEntity, 'lowestPriorPrice', \Scayle\StorefrontApi\Models\LowestPriorPrice::class); 25 | $this->assertPropertyHasTheCorrectType($responseEntity, 'siblings', \Scayle\StorefrontApi\Models\Product::class); 26 | $this->assertPropertyHasTheCorrectType($responseEntity, 'baseCategories', \Scayle\StorefrontApi\Models\BaseCategory::class); 27 | $this->assertPropertyHasTheCorrectType($responseEntity, 'variants', \Scayle\StorefrontApi\Models\Variant::class); 28 | 29 | 30 | 31 | } 32 | 33 | public function testQuery() 34 | { 35 | $responseEntity = $this->api->products->Query( []); 36 | 37 | $expectedResponseJson = $this->loadFixture('ProductQueryResponse.json'); 38 | $this->assertInstanceOf(\Scayle\StorefrontApi\Models\ProductsResponse::class, $responseEntity); 39 | $this->assertJsonStringEqualsJsonString(json_encode($expectedResponseJson), $responseEntity->toJson()); 40 | 41 | $this->assertPropertyHasTheCorrectType($responseEntity, 'attributes', \Scayle\StorefrontApi\Models\Attribute::class); 42 | $this->assertPropertyHasTheCorrectType($responseEntity, 'advancedAttributes', \Scayle\StorefrontApi\Models\AdvancedAttribute::class); 43 | $this->assertPropertyHasTheCorrectType($responseEntity, 'categories', \Scayle\StorefrontApi\Models\ProductCategory::class); 44 | $this->assertPropertyHasTheCorrectType($responseEntity, 'definingAttributes', \Scayle\StorefrontApi\Models\DefiningAttribute::class); 45 | $this->assertPropertyHasTheCorrectType($responseEntity, 'images', \Scayle\StorefrontApi\Models\Image::class); 46 | $this->assertPropertyHasTheCorrectType($responseEntity, 'priceRange', \Scayle\StorefrontApi\Models\PriceRange::class); 47 | $this->assertPropertyHasTheCorrectType($responseEntity, 'reductionRange', \Scayle\StorefrontApi\Models\ReductionRange::class); 48 | $this->assertPropertyHasTheCorrectType($responseEntity, 'lowestPriorPrice', \Scayle\StorefrontApi\Models\LowestPriorPrice::class); 49 | $this->assertPropertyHasTheCorrectType($responseEntity, 'siblings', \Scayle\StorefrontApi\Models\Product::class); 50 | $this->assertPropertyHasTheCorrectType($responseEntity, 'baseCategories', \Scayle\StorefrontApi\Models\BaseCategory::class); 51 | $this->assertPropertyHasTheCorrectType($responseEntity, 'variants', \Scayle\StorefrontApi\Models\Variant::class); 52 | 53 | 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /tests/BaseApiTestCase.php: -------------------------------------------------------------------------------- 1 | api = new StorefrontClient([ 22 | 'apiUrl' => getenv('API_URL') ? getenv('API_URL') : 'http://127.0.0.1:4010', 23 | 'accessToken' => 'abc123', 24 | ]); 25 | } 26 | 27 | /** 28 | * Gets a protected property from an ApiObject 29 | * 30 | * @param \Scayle\StorefrontApi\Models\ApiObject $apiObject 31 | * @param string $propertyName 32 | * 33 | * @return mixed|null 34 | * 35 | * @throws \ReflectionException 36 | */ 37 | private function getProtectedProperty($apiObject, $propertyName) 38 | { 39 | $reflect = new \ReflectionClass($apiObject); 40 | 41 | $props = $reflect->getProperties(ReflectionProperty::IS_PROTECTED); 42 | 43 | foreach ($props as $prop) { 44 | if ($prop->getName() === $propertyName) { 45 | $prop->setAccessible(true); 46 | 47 | return $prop->getValue($apiObject); 48 | } 49 | } 50 | 51 | return null; 52 | } 53 | 54 | /** 55 | * @param \Scayle\StorefrontApi\Models\ApiObject $apiObject an ApiObject instance 56 | * @param string $propertyName the property to type check 57 | * @param string $className the expected classname 58 | * @throws \ReflectionException 59 | */ 60 | protected function assertPropertyHasTheCorrectType($apiObject, $propertyName, $className) 61 | { 62 | $attributes = $this->getProtectedProperty($apiObject, '_attributes'); 63 | 64 | foreach ($attributes as $objPropertyName => $objPropertyValue) { 65 | if ($objPropertyName === $propertyName) { 66 | if (is_array($objPropertyValue)) { 67 | foreach ($objPropertyValue as $objPropertyItem) { 68 | $this->assertInstanceOf($className, $objPropertyItem); 69 | } 70 | } else { 71 | $this->assertInstanceOf($className, $objPropertyValue); 72 | } 73 | 74 | break; 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * @param ApiObject $apiObject an ApiObject instance 81 | * @param string $propertyName the property to type check 82 | * @param string $discriminator the discriminator field name 83 | * @param array $mapping mapping of discriminator value to concrete class 84 | * @throws \ReflectionException 85 | */ 86 | protected function assertPropertyHasCorrectPolymorphicType($apiObject, $propertyName, $discriminator, $mapping) 87 | { 88 | $attributes = $this->getProtectedProperty($apiObject, '_attributes'); 89 | 90 | foreach ($attributes as $objPropertyName => $objPropertyValue) { 91 | if ($objPropertyName === $propertyName) { 92 | if (is_array($objPropertyValue)) { 93 | foreach ($objPropertyValue as $objPropertyItem) { 94 | $discriminatorValue = $objPropertyItem->{$discriminator}; 95 | $this->assertArrayHasKey($discriminatorValue, $mapping); 96 | $className = $mapping[$discriminatorValue]; 97 | $this->assertInstanceOf($className, $objPropertyItem); 98 | } 99 | } else { 100 | $discriminatorValue = $objPropertyValue->{$discriminator}; 101 | $className = $mapping[$discriminatorValue]; 102 | $this->assertArrayHasKey($discriminatorValue, $mapping); 103 | $this->assertInstanceOf($className, $objPropertyValue); 104 | } 105 | 106 | break; 107 | } 108 | } 109 | } 110 | 111 | protected function loadFixture(string $filename) : array 112 | { 113 | $filename = __DIR__ . '/fixtures/' . $filename; 114 | $this->assertFileExists($filename, "Fixtures do not exist. Are you sure you have valid request and response examples in your OpenAPI specification?"); 115 | return json_decode(file_get_contents($filename), true); 116 | } 117 | } -------------------------------------------------------------------------------- /lib/AbstractApi.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | private $config; 37 | 38 | /** 39 | * AbstractAdminApi constructor. 40 | * @param array $config 41 | * @example ['apiUrl' => 'http://cloud.aboutyou.com', 'accessToken' => 'myToken'] 42 | * @param ClientInterface $httpClient 43 | */ 44 | public function __construct($config = [], $httpClient = null) 45 | { 46 | $this->validateConfig($config); 47 | 48 | $this->config = $config; 49 | $this->httpClient = $httpClient ?: new Client(); 50 | } 51 | 52 | /** 53 | * @return string 54 | */ 55 | public function getApiUrl() 56 | { 57 | return $this->config[self::API_URL]; 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function getAuth() 64 | { 65 | return $this->config[self::AUTH]; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function getShopId() 72 | { 73 | return $this->config[self::SHOP_ID]; 74 | } 75 | 76 | /** 77 | * @param string $method 78 | * @param string $relativePath 79 | * @param array $options 80 | * @param null|string $body 81 | * 82 | * @return ResponseInterface 83 | * 84 | * @throws ClientExceptionInterface 85 | */ 86 | public function request($method, $relativePath, $options = [], $body = null) 87 | { 88 | $url = $this->getApiUrl() . $relativePath . $this->makeQueryString($options); 89 | 90 | $headers = $this->getAuthHeader(); 91 | $headers[self::SHOP_HEADER_NAME] = $this->getShopId(); 92 | $headers['Content-Type'] = 'application/json'; 93 | $headers['Accept'] = 'application/json, */*'; 94 | $headers['X-SDK'] = 'php/' . self::VERSION; 95 | 96 | $request = new Request($method, $url, $headers, $body); 97 | return $this->httpClient->sendRequest($request); 98 | } 99 | 100 | private function getAuthHeader() 101 | { 102 | $auth = $this->getAuth(); 103 | if (array_key_exists(self::AUTH_TOKEN, $auth)) { 104 | return [ 105 | self::AUTH_HEADER_NAME => $auth[self::AUTH_TOKEN], 106 | ]; 107 | } 108 | 109 | $credentials = base64_encode($auth[self::AUTH_USERNAME] . ':' . $auth[self::AUTH_PASSWORD]); 110 | 111 | return [ 112 | 'Authorization' => 'Basic ' . $credentials, 113 | ]; 114 | } 115 | 116 | private function makeQueryString(array $options): string 117 | { 118 | if (empty($options)) { 119 | return ''; 120 | } 121 | 122 | foreach ($options as &$value) { 123 | if (is_bool($value)) { 124 | $value = $value ? 'true' : 'false'; 125 | } 126 | } 127 | 128 | unset($value); 129 | 130 | return '?' . http_build_query($options); 131 | } 132 | 133 | /** 134 | * @param array $config 135 | * 136 | * @throws InvalidArgumentException 137 | */ 138 | private function validateConfig($config) 139 | { 140 | if (empty($config[self::API_URL])) { 141 | $message = sprintf('%s cannot be empty', self::API_URL); 142 | throw new InvalidArgumentException($message); 143 | } 144 | 145 | if (empty($config[self::SHOP_ID])) { 146 | $message = sprintf('%s cannot be empty', self::SHOP_ID); 147 | throw new InvalidArgumentException($message); 148 | } 149 | 150 | if (empty($config[self::AUTH])) { 151 | $message = sprintf('%s cannot be empty', self::AUTH); 152 | throw new InvalidArgumentException($message); 153 | } 154 | 155 | $auth = $config[self::AUTH]; 156 | if ((empty($auth[self::AUTH_TYPE]) || empty($auth[self::AUTH_TOKEN])) && (empty($auth[self::AUTH_USERNAME]) || empty($auth[self::AUTH_PASSWORD]))) { 157 | $message = sprintf('%s array must consist of either type and token or username and password', self::AUTH); 158 | throw new InvalidArgumentException($message); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /lib/Models/ApiObject.php: -------------------------------------------------------------------------------- 1 | serializer = new \Scayle\StorefrontApi\Serializers\ModelSerializer(); 50 | $attributes = $this->mergeDefaultValues($attributes); 51 | $this->_attributes = $this->unserialize($attributes); 52 | } 53 | 54 | public function __set($name, $value) 55 | { 56 | $this->_attributes[$name] = $value; 57 | } 58 | 59 | public function &__get($name) 60 | { 61 | if (array_key_exists($name, $this->_attributes)) { 62 | return $this->_attributes[$name]; 63 | } 64 | 65 | $nullRef = null; 66 | return $nullRef; 67 | } 68 | 69 | public function __isset($name) 70 | { 71 | return isset($this->_attributes[$name]); 72 | } 73 | 74 | /** 75 | * @return array 76 | */ 77 | public function jsonSerialize() 78 | { 79 | $serialized = []; 80 | 81 | foreach ($this->_attributes as $key => $value) { 82 | if ($value instanceof ApiObject) { 83 | $value = $value->jsonSerialize(); 84 | } 85 | 86 | $serialized[$key] = $value; 87 | } 88 | 89 | return $serialized; 90 | } 91 | 92 | /** 93 | * @return false|string 94 | */ 95 | public function toJson() 96 | { 97 | return json_encode($this->_attributes); 98 | } 99 | 100 | /** 101 | * @param array $attributes 102 | * 103 | * @return array mixed 104 | */ 105 | private function mergeDefaultValues($attributes) 106 | { 107 | $diff = array_diff_key($this->defaultValues, $attributes); 108 | $attributes = array_merge($attributes, $diff); 109 | 110 | return $attributes; 111 | } 112 | 113 | /** 114 | * @param array $attributes 115 | * @return array 116 | */ 117 | private function unserialize($attributes) 118 | { 119 | $unserialized = []; 120 | 121 | foreach ($attributes as $key => $value) { 122 | if (is_null($value)) { 123 | $unserialized[$key] = $value; 124 | continue; 125 | } 126 | 127 | // Handle nested single object instantiation 128 | if (array_key_exists($key, $this->classMap)) { 129 | $value = new $this->classMap[$key]($value); 130 | } 131 | 132 | // Handle nested object collection instantiation 133 | if (array_key_exists($key, $this->collectionClassMap)) { 134 | $nestedObjects = []; 135 | foreach ($value as $nestedKey => $nestedValue) { 136 | $nestedObjects[$nestedKey] = $this->serializer->parse($this->collectionClassMap[$key], $nestedValue); 137 | } 138 | 139 | $value = $nestedObjects; 140 | } 141 | 142 | if (array_key_exists($key, $this->collection2dClassMap)) { 143 | $nestedObjects = []; 144 | foreach ($value as $nestedValue) { 145 | $nestedObjects[] = array_map(fn($v) => new $this->collection2dClassMap[$key]($v), $nestedValue); 146 | } 147 | 148 | $value = $nestedObjects; 149 | } 150 | 151 | 152 | // Handle single nested object polymorphism 153 | if (array_key_exists($key, $this->polymorphic)) { 154 | $discriminator = $this->polymorphic[$key]['discriminator']; 155 | $discriminatorValue = $attributes[$discriminator]; 156 | $className = $this->polymorphic[$key]['mapping'][$discriminatorValue]; 157 | $value = new $className([$key => $value]); 158 | } 159 | 160 | // Handle nested object collection polymorphism 161 | if (array_key_exists($key, $this->polymorphicCollections)) { 162 | $discriminator = $this->polymorphicCollections[$key]['discriminator']; 163 | $objects = []; 164 | foreach ($attributes[$key] as $nestedAttribute) { 165 | $discriminatorValue = $nestedAttribute[$discriminator]; 166 | if (array_key_exists($discriminatorValue, $this->polymorphicCollections[$key]['mapping'])) { 167 | $className = $this->polymorphicCollections[$key]['mapping'][$discriminatorValue]; 168 | $objects[] = new $className($nestedAttribute); 169 | } 170 | } 171 | 172 | $value = $objects; 173 | } 174 | 175 | $unserialized[$key] = $value; 176 | } 177 | 178 | return $unserialized; 179 | } 180 | } 181 | 182 | --------------------------------------------------------------------------------