Skip to content

Commit 7f13d27

Browse files
FFWEB-2661: Proxy
1 parent 8d4f65d commit 7f13d27

13 files changed

Lines changed: 353 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Changelog
2+
## Unreleased
3+
### Add
4+
- Proxy
5+
26
## [v4.2.1] - 2023.03.10
37
### Fix
48
- Category

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ final chapter *Exporting Feed* describes how to use provided console command to
4343
- [Split ASN on Category Page](#split-asn-on-category-page)
4444
- [Set custom Field Roles](#set-custom-field-roles)
4545
- [Adding custom Export Cache Subscriber](#adding-custom-export-cache-subscriber)
46+
- [Enrich data received from FACT-Finder in ProxyController](#enrich-data-received-from-fact-finder-in-proxycontroller)
4647
- [Contribute](#contribute)
4748
- [License](#license)
4849

@@ -120,6 +121,11 @@ Without SSR enabled, web crawlers could not have a chance to scan the element re
120121

121122
This section contains a plugin configuration, which is optional and provides additional features.
122123

124+
* Use Proxy? - By enabling this, once a search query is sent, it does not immediately reach FACT-Finder, but is handed off to a specific controller
125+
`src/Storefront/Controller/ProxyController.php` which hands the request to the FACT-Finder system, receives the answer, processes it and only then returns it to the frontend view. You can use `EnrichProxyDataEvent` to enrich data received from FACT-Finder. You can find more
126+
details about implementation [here](#enrich-data-received-from-fact-finder-in-proxycontroller).
127+
128+
Note: Sending each request to FACT-Finder instance trough Shopware, you lose on performance as each request need to be handled first by HTTP server and then, by Shopware itself. This additional traffic could be easily avoided by not activating this feature if there's no clear reason to use it.
123129
* Scenario how to count single click on "Add to cart" button
124130
* Redirect mapping for selected queries - put each pair `query=url` in separate row. If the phrase appears twice, the first one from the top of the list will be taken. Url can be relative path `/some/page` or absolute url `https://domain.com/some/page?someParameter=1`. If provided pair has an invalid format then it will be ignored.
125131

@@ -613,6 +619,33 @@ class FeedPreprocessorEntryBeforeCreateSubscriber implements EventSubscriberInte
613619

614620
```
615621

622+
### Enrich data received from FACT-Finder in ProxyController
623+
624+
```php
625+
626+
use Omikron\FactFinder\Shopware6\Events\EnrichProxyDataEvent;
627+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
628+
629+
class EnrichProxyDataEventSubscriber implements EventSubscriberInterface
630+
{
631+
public static function getSubscribedEvents()
632+
{
633+
return [EnrichProxyDataEvent::class => 'enrichData'];
634+
}
635+
636+
public function enrichData(EnrichProxyDataEvent $event)
637+
{
638+
$data = $event->getData();
639+
$data['example_data'] = [
640+
'some_data' => 'data_1',
641+
'some_data2' => 'data_2',
642+
];
643+
$event->setData($data);
644+
}
645+
}
646+
647+
```
648+
616649

617650
## Contribute
618651

docs/assets/advanced-settings.png

4.45 KB
Loading
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace spec\Omikron\FactFinder\Shopware6\Storefront\Controller;
6+
7+
use GuzzleHttp\Exception\ConnectException;
8+
use GuzzleHttp\Psr7\Stream;
9+
use Omikron\FactFinder\Communication\Client\ClientBuilder;
10+
use Omikron\FactFinder\Communication\Client\ClientInterface;
11+
use Omikron\FactFinder\Shopware6\Config\Communication;
12+
use Omikron\FactFinder\Shopware6\Events\BeforeProxyErrorResponseEvent;
13+
use Omikron\FactFinder\Shopware6\Events\EnrichProxyDataEvent;
14+
use PhpSpec\ObjectBehavior;
15+
use PhpSpec\Wrapper\Collaborator;
16+
use PHPUnit\Framework\Assert;
17+
use Prophecy\Argument;
18+
use Psr\Http\Client\ClientExceptionInterface;
19+
use Psr\Http\Message\RequestInterface;
20+
use Psr\Http\Message\ResponseInterface;
21+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22+
use Symfony\Component\HttpFoundation\JsonResponse;
23+
use Symfony\Component\HttpFoundation\Request;
24+
use Symfony\Component\HttpFoundation\Response;
25+
26+
class ProxyControllerSpec extends ObjectBehavior
27+
{
28+
private Collaborator $client;
29+
private Collaborator $clientBuilder;
30+
31+
public function let(
32+
Communication $config,
33+
ClientInterface $client,
34+
ClientBuilder $clientBuilder
35+
) {
36+
$serverUrl = 'https://example.fact-finder.de/fact-finder';
37+
$config->getServerUrl()->willReturn($serverUrl);
38+
$config->getVersion()->willReturn('ng');
39+
$config->getCredentials()->willReturn([
40+
'username',
41+
'pass',
42+
]);
43+
$this->beConstructedWith($config);
44+
$clientBuilder->build()->willReturn($client);
45+
$clientBuilder->withServerUrl(Argument::any())->willReturn($clientBuilder);
46+
$clientBuilder->withCredentials(Argument::any())->willReturn($clientBuilder);
47+
$clientBuilder->withVersion(Argument::any())->willReturn($clientBuilder);
48+
$this->client = $client;
49+
$this->clientBuilder = $clientBuilder;
50+
}
51+
52+
public function it_should_return_success_response(
53+
Request $request,
54+
ResponseInterface $response,
55+
EventDispatcherInterface $eventDispatcher,
56+
EnrichProxyDataEvent $event,
57+
Stream $stream
58+
) {
59+
// Expect & Given
60+
$request->getMethod()->willReturn(Request::METHOD_GET);
61+
$uri = 'rest/v5/search/example_channel?query=bag&sid=123&format=json';
62+
$_SERVER['REQUEST_URI'] = sprintf('/fact-finder/proxy/%s', $uri);
63+
$this->client->request(Request::METHOD_GET, $uri)->willReturn($response);
64+
$jsonResponse = file_get_contents(dirname(__DIR__, 2) . '/data/proxy/search-bag.json');
65+
$responseData = json_decode($jsonResponse, true);
66+
$stream->__toString()->willReturn($jsonResponse);
67+
$response->getBody()->willReturn($stream);
68+
$event->getData()->willReturn($responseData);
69+
$eventDispatcher->dispatch(Argument::type(EnrichProxyDataEvent::class))->willReturn($event);
70+
71+
// When
72+
$response = $this->execute('rest/v5/search/example_channel', $request, $this->clientBuilder, $eventDispatcher);
73+
74+
// Then
75+
$response->shouldBeAnInstanceOf(JsonResponse::class);
76+
Assert::assertEquals($responseData, json_decode($response->getWrappedObject()->getContent(), true));
77+
}
78+
79+
public function it_should_return_error_response(
80+
Request $request,
81+
EventDispatcherInterface $eventDispatcher,
82+
BeforeProxyErrorResponseEvent $event,
83+
RequestInterface $requestInterface
84+
) {
85+
// Expect & Given
86+
$request->getMethod()->willReturn(Request::METHOD_GET);
87+
$uri = 'rest/v5/search/example_channel?query=bag&sid=123&format=json';
88+
$_SERVER['REQUEST_URI'] = sprintf('/fact-finder/proxy/%s', $uri);
89+
$this->client->request(Request::METHOD_GET, $uri)->willThrow(new ConnectException('Unable to connect with server.', $requestInterface->getWrappedObject()));
90+
$eventDispatcher->dispatch(Argument::type(BeforeProxyErrorResponseEvent::class))->willReturn($event);
91+
92+
// When
93+
$response = $this->execute('rest/v5/search/example_channel', $request, $this->clientBuilder, $eventDispatcher);
94+
95+
// Then
96+
$response->shouldBeAnInstanceOf(JsonResponse::class);
97+
Assert::assertEquals(
98+
['message' => 'Unable to connect with server.'],
99+
json_decode($response->getWrappedObject()->getContent(), true)
100+
);
101+
Assert::assertEquals(
102+
Response::HTTP_BAD_REQUEST,
103+
$response->getWrappedObject()->getStatusCode()
104+
);
105+
}
106+
}

spec/Subscriber/ConfigurationSubscriberSpec.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
use Shopware\Storefront\Page\GenericPageLoadedEvent;
2424
use Shopware\Storefront\Page\Page;
2525
use Symfony\Component\HttpFoundation\Request;
26+
use Symfony\Component\Routing\RouterInterface;
2627

2728
class ConfigurationSubscriberSpec extends ObjectBehavior
2829
{
29-
function let(
30+
public function let(
3031
Communication $communication,
31-
ExtensionConfig $extensionConfig
32+
ExtensionConfig $extensionConfig,
33+
RouterInterface $router
3234
) {
3335
$fieldRoles = [];
3436
$communicationParameters = [];
@@ -41,10 +43,10 @@ function let(
4143
]
4244
);
4345
$extensionConfig->getRedirectMapping()->willReturn(new RedirectMapping(''));
44-
$this->beConstructedWith($communication, $extensionConfig, $fieldRoles, $communicationParameters);
46+
$this->beConstructedWith($communication, $extensionConfig, $router, $fieldRoles, $communicationParameters);
4547
}
4648

47-
function it_will_return_factfinderchannel_for_specific_sales_channel_id(
49+
public function it_will_return_factfinderchannel_for_specific_sales_channel_id(
4850
Communication $communication,
4951
GenericPageLoadedEvent $event,
5052
SalesChannelContext $salesChannelContext,
@@ -64,6 +66,7 @@ function it_will_return_factfinderchannel_for_specific_sales_channel_id(
6466
$communication->getVersion()->willReturn(Version::NG);
6567
$communication->getApiVersion()->willReturn('v5');
6668
$communication->isSsrActive()->willReturn(false);
69+
$communication->isProxyEnabled()->willReturn(false);
6770
$communication->getChannel('main_sales_channel')->willReturn('some_ff_channel');
6871
$communication->getFieldRoles(Argument::any())->willReturn([]);
6972
$event->getRequest()->willReturn($request);
@@ -85,7 +88,7 @@ function it_will_return_factfinderchannel_for_specific_sales_channel_id(
8588
$this->onPageLoaded($event);
8689
}
8790

88-
function it_will_add_page_extension_for_storefront_render_event(
91+
public function it_will_add_page_extension_for_storefront_render_event(
8992
Communication $communication,
9093
StorefrontRenderEvent $event,
9194
SalesChannelContext $salesChannelContext,
@@ -108,6 +111,7 @@ function it_will_add_page_extension_for_storefront_render_event(
108111
$communication->getVersion()->willReturn(Version::NG);
109112
$communication->getApiVersion()->willReturn('v5');
110113
$communication->isSsrActive()->willReturn(false);
114+
$communication->isProxyEnabled()->willReturn(false);
111115
$event->getRequest()->willReturn($request);
112116
$request->get('_route', Argument::any())->willReturn('factfinder');
113117
$request->getLocale()->willReturn('en');
@@ -127,7 +131,7 @@ function it_will_add_page_extension_for_storefront_render_event(
127131
$this->onPageLoaded($event);
128132
}
129133

130-
function it_will_throw_exception_when_event_does_not_have_page(
134+
public function it_will_throw_exception_when_event_does_not_have_page(
131135
Communication $communication,
132136
StorefrontRenderEvent $event,
133137
SalesChannelContext $salesChannelContext,
@@ -148,6 +152,7 @@ function it_will_throw_exception_when_event_does_not_have_page(
148152
$communication->getVersion()->willReturn(Version::NG);
149153
$communication->getApiVersion()->willReturn('v5');
150154
$communication->isSsrActive()->willReturn(false);
155+
$communication->isProxyEnabled()->willReturn(false);
151156
$event->getRequest()->willReturn($request);
152157
$request->get('_route', Argument::any())->willReturn('factfinder');
153158
$request->getLocale()->willReturn('en');

spec/data/proxy/search-bag.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"totalHits": 3,
3+
"hits": [
4+
{
5+
"masterValues": {
6+
"Master": "6237eeb757e2485ca589c30457f09493",
7+
"Name": "\"Durable Wool Bag\""
8+
},
9+
"id": "6237eeb757e2485ca589c30457f09493"
10+
},
11+
{
12+
"masterValues": {
13+
"Master": "0636632e55a1411987c8a289a05e5f0d",
14+
"Name": "\"Incredible Aluminum Bag of Scones\""
15+
},
16+
"id": "0636632e55a1411987c8a289a05e5f0d"
17+
},
18+
{
19+
"masterValues": {
20+
"Master": "a3b45c47356f4fc29579538fc4cc2c6b",
21+
"Name": "\"Rustic Iron Her Majesty’s Little Black Handbag\""
22+
},
23+
"id": "a3b45c47356f4fc29579538fc4cc2c6b"
24+
}
25+
]
26+
}

src/Config/Communication.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ public function getApiVersion(): string
4545
{
4646
return (string) $this->config('apiVersion') ?? 'v4';
4747
}
48+
49+
public function isProxyEnabled(): bool
50+
{
51+
return (bool) $this->config('useProxy');
52+
}
4853
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Omikron\FactFinder\Shopware6\Events;
6+
7+
use Symfony\Component\HttpFoundation\JsonResponse;
8+
use Symfony\Contracts\EventDispatcher\Event;
9+
10+
class BeforeProxyErrorResponseEvent extends Event
11+
{
12+
private JsonResponse $response;
13+
14+
public function __construct(JsonResponse $response)
15+
{
16+
$this->response = $response;
17+
}
18+
19+
public function getResponse(): JsonResponse
20+
{
21+
return $this->response;
22+
}
23+
24+
public function setResponse(JsonResponse $response): void
25+
{
26+
$this->response = $response;
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Omikron\FactFinder\Shopware6\Events;
6+
7+
use Symfony\Contracts\EventDispatcher\Event;
8+
9+
class EnrichProxyDataEvent extends Event
10+
{
11+
private array $data;
12+
13+
public function __construct(array $data)
14+
{
15+
$this->data = $data;
16+
}
17+
18+
public function getData(): array
19+
{
20+
return $this->data;
21+
}
22+
23+
public function setData(array $data): void
24+
{
25+
$this->data = $data;
26+
}
27+
}

src/Resources/config/config.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@
6666
<title>Advanced Settings</title>
6767
<title lang="de-DE">Erweiterte Einstellungen</title>
6868

69+
<input-field type="bool">
70+
<name>useProxy</name>
71+
<label>Use Proxy?</label>
72+
<label lang="de-DE">Use Proxy?</label>
73+
<helpText>Using Proxy allows you to refine the data coming back from FACT-Finder inside the SDK code. Please refer the documentation for more info.</helpText>
74+
</input-field>
75+
6976
<input-field type="single-select">
7077
<name>trackingAddToCartCount</name>
7178
<options>

0 commit comments

Comments
 (0)