Skip to content

Commit 8c81872

Browse files
committed
Added Symfony quickstart Readme.
1 parent 4cd4dbd commit 8c81872

File tree

3 files changed

+168
-37
lines changed

3 files changed

+168
-37
lines changed

.github/workflows/Quickstart Symfony.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,7 @@ jobs:
8787
echo "$app[appid]\n";
8888
}
8989
},
90-
Response::HTTP_OK,
91-
['content-type' => 'text/plain'],
90+
headers: ['content-type' => 'text/plain'],
9291
);
9392
}
9493
}

docs/Quickstart Symfony.md

Lines changed: 164 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,199 @@
11
Porter Quick Start Guide for Symfony
22
====================================
33

4-
This quick start guide will walk through integrating Porter into a new Symfony project from scratch and assumes you already have a PHP environment set up with Composer. Let's start by initializing our Composer file by running `composer init` in our project's root directory and accepting the defaults. We can skip defining dependencies interactively because we'll issue separate commands in a moment.
4+
This quick start guide will walk through integrating Porter into a new Symfony project from scratch and assumes we already have a PHP 8.1 environment set up with Composer. This guide is based on a real-world use-case, used in production on [Steam 250][]. If we want to integrate Porter into an existing Symfony project, simply skip the Composer steps. If you encounter any other errors or get stuck, don't hesitate to file an issue.
55

6-
Let's start with the [European Central Bank][ECB provider] (ECB) provider by including it in our `composer.json` with the following command.
6+
Let's start by creating a new Symfony 5 project in an empty directory using the following command. Ensure the current working directory is set to the empty project directory.
77

88
```sh
9-
composer require provider/european-central-bank
9+
composer create-project symfony/skeleton . ^5
1010
```
1111

12-
We now have the provider installed along with all its dependencies, including Porter herself. We want to create a `new Porter` instance now, but we need to pass a `ContainerInterface` to her constructor. Any PSR-11 container is valid, but let's use Joomla DI for now.
12+
>Note: The Steam provider (used below) requires [Amp v3][], which is currently in beta, so we need to allow beta dependencies temporarily. This can be enabled with the following command.
13+
> ```sh
14+
> composer config minimum-stability beta
15+
> composer config prefer-stable true
16+
> ```
17+
18+
Let's start with the [Steam provider][] for Porter by including it in our `composer.json` with the following command.
1319
1420
```sh
15-
composer require joomla/di
21+
composer require --with-dependencies provider/steam
1622
```
1723
18-
Create a new container and register an instance of `EuropeanCentralBankProvider` with it. Pass the container to a new Porter instance. Don't forget to include the autoloader!
24+
>Note: We specify the *with dependencies* flag because some shared dependencies between the provider and our current Symfony project may have mismatched versions, so they must be allowed to up/downgrade as necessary to work together.
25+
26+
Now the provider is installed along with all its dependencies, including Amp and Porter herself.
27+
28+
In this simple exercise, we will use the Steam provider to display a list of all the app IDs for every app available on [Steam][]. We're going to start coding now, so let's fire up our favourite editor. Start by creating a new `AppListAction` in our existing `src/Controller` directory. We're following the [ADR][] pattern rather than MVC, so we could rename the *Controller* directory to *Action*, too, but we will refrain for now, to keep things simple. Actions only handle a single route, using the `__invoke` method, so let's add that, too.
1929
2030
```php
21-
use Joomla\DI\Container;
22-
use ScriptFUSION\Porter\Porter;
23-
use ScriptFUSION\Porter\Provider\EuropeanCentralBank\Provider\EuropeanCentralBankProvider;
31+
<?php
32+
declare(strict_types=1);
33+
34+
namespace App\Action;
2435
25-
require 'vendor/autoload.php';
36+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
2637
27-
$container = new Container;
28-
$container->set(EuropeanCentralBankProvider::class, new EuropeanCentralBankProvider);
38+
final class AppListAction extends AbstractController
39+
{
40+
public function __invoke()
41+
{
42+
}
43+
}
44+
```
45+
46+
Let's make our new action act as the home page for our application by making it respond to the `/` route path.
2947
30-
$porter = new Porter($container);
48+
```diff
49+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
50+
+use Symfony\Component\Routing\Annotation\Route;
51+
52+
+ #[Route('/')]
53+
public function __invoke()
3154
```
3255
33-
We're now ready to import any of the ECB's resources. Let's import the latest daily foreign exchange rates provided by `DailyForexRates`. Porter's `import()` method requires a `Import` that accepts the resource we want to import.
56+
We're using annotations because they're easiest to implement, but in order for this to work, we need to ensure the Doctrine annotations library is installed.
3457

35-
```php
36-
use ScriptFUSION\Porter\Import\Import;
37-
use ScriptFUSION\Porter\Provider\EuropeanCentralBank\Provider\Resource\DailyForexRates;
58+
```sh
59+
composer require doctrine/annotations
60+
```
61+
62+
Let's just fill in the rest of the method with a stub, so we can test our application is working so far. The complete list of Steam app IDs is very long (over 150,000) so we will want to use a `StreamedResponse`.
63+
64+
```diff
65+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
66+
+use Symfony\Component\HttpFoundation\Response;
67+
+use Symfony\Component\HttpFoundation\StreamedResponse;
68+
use Symfony\Component\Routing\Annotation\Route;
69+
70+
- public function __invoke()
71+
+ public function __invoke(): Response
72+
{
73+
+ return new StreamedResponse(
74+
+ fn () => print 'Hello, Porter!',
75+
+ );
76+
}
77+
```
78+
79+
Start a web server to [view the home page](http://localhost).
80+
81+
```sh
82+
php -S localhost:80 public/index.php
83+
```
84+
85+
We should see *Hello, Porter!* displayed in our browser. If you see the default Symfony *welcome* page, ensure `doctrine/annotations` was installed correctly.
86+
87+
To gain access to the Steam data we need to inject Porter into our action.
3888

39-
$rates = $porter->import(new Import(new DailyForexRates));
89+
```diff
90+
+use ScriptFUSION\Porter\Porter;
91+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
92+
93+
- public function __invoke(): Response
94+
+ public function __invoke(Porter $porter): Response
95+
```
96+
97+
Refreshing our browser now will display an error because we haven't told Symfony how to configure Porter as a service yet:
98+
99+
>`RuntimeException` Cannot autowire argument $porter of "App\Controller\AppListAction()": it references class "ScriptFUSION\Porter\Porter" but no such service exists.
100+
101+
Let's append the Porter service to `config/services.yaml`. Porter requires a container of providers, so let's inject the *providers* service into Porter.
102+
103+
```yaml
104+
ScriptFUSION\Porter\Porter:
105+
arguments:
106+
- '@providers'
107+
```
108+
109+
Of course, this *providers* service does not exist yet, but we can create it by [defining a service locator][]. We only have one provider at this time, the `SteamProvider`, so let's ensure it's added to the locator so Porter can use it.
110+
111+
```yaml
112+
providers:
113+
class: Symfony\Component\DependencyInjection\ServiceLocator
114+
arguments:
115+
-
116+
- '@ScriptFUSION\Porter\Provider\Steam\SteamProvider'
117+
```
118+
119+
>Note: We do not use the `!service_locator` shortcut to implicitly create the service locator due to a [Symfony bug](https://github.com/symfony/symfony/issues/48454) that misnames services added in this way.
120+
121+
Finally, since `SteamProvider` is third-party code, Symfony requires us to explicitly register it as a service, but we don't need to customize it in any way, so we can just specify its configuration as the tilde (`~`) to use the defaults.
122+
123+
```yaml
124+
ScriptFUSION\Porter\Provider\Steam\SteamProvider: ~
125+
```
126+
127+
Refreshing our browser now should recompile the Symfony DI container and show us the same message as before (without errors). Porter is now injected into our action, ready to use!
128+
129+
```diff
130+
use ScriptFUSION\Porter\Porter;
131+
+use ScriptFUSION\Porter\Provider\Steam\Resource\GetAppList;
132+
+use ScriptFUSION\Porter\Specification\ImportSpecification;
133+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
134+
135+
- fn () => print 'Hello, Porter!',
136+
+ function () use ($porter): void {
137+
+ foreach ($porter->import(new ImportSpecification(new GetAppList())) as $app) {
138+
+ echo "$app[appid]\n";
139+
+ }
140+
+ },
40141
```
41142

42-
Porter returns an iterator, so we can now loop over the rates and print them out.
143+
We iterate over each result from importing `GetAppList` and emit it using `echo`, appending a new line. Viewing the results in our browser shows us lots of numbers (the ID of each Steam app) but it does not respect the new line character (`\n`) because it renders as HTML by default. Let's fix that by specifying the correct mime type. Below is the completed code, including the new `content-type` header:
43144

44145
```php
45-
foreach ($rates as $rate) {
46-
echo "$rate[currency]: $rate[rate]\n";
146+
<?php
147+
declare(strict_types=1);
148+
149+
namespace App\Controller;
150+
151+
use ScriptFUSION\Porter\Porter;
152+
use ScriptFUSION\Porter\Provider\Steam\Resource\GetAppList;
153+
use ScriptFUSION\Porter\Specification\ImportSpecification;
154+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
155+
use Symfony\Component\HttpFoundation\Response;
156+
use Symfony\Component\HttpFoundation\StreamedResponse;
157+
use Symfony\Component\Routing\Annotation\Route;
158+
159+
final class AppListAction extends AbstractController
160+
{
161+
#[Route('/')]
162+
public function __invoke(Porter $porter): Response
163+
{
164+
return new StreamedResponse(
165+
function () use ($porter): void {
166+
foreach ($porter->import(new ImportSpecification(new GetAppList())) as $app) {
167+
echo "$app[appid]\n";
168+
}
169+
},
170+
headers: ['content-type' => 'text/plain'],
171+
);
172+
}
47173
}
48174
```
49175

50-
This outputs something similar to the following, with today's current rates.
176+
This should output a long list of numbers, one on each line, in the browser. For example:
51177

52-
>USD: 1.2304
53-
JPY: 131.66
54-
BGN: 1.9558
55-
CZK: 25.357
56-
DKK: 7.4469
178+
>2170321
179+
1825161
180+
1897482
181+
2112761
182+
1829051
57183
...
58184

59-
Since these rates come from the European Central Bank, they're relative to the Euro (EUR), which is assumed to always be *1*. We can use this information to write a currency converter that's always up-to-date with the latest exchange rate information.
185+
We now have a Porter service defined that can be injected into as many services or actions as we wish. We can add as many [providers] to the `providers` service locator as we want, without any performance impact, since each service is lazy-loaded when required.
60186

61-
This just scratches the surface of Porter without going into any details. Explore the [rest of the manual][Readme] at your leisure to gain a fuller understanding of the features at your disposal.
187+
This just scratches the surface of Porter without going into any details. Explore the [rest of the manual][Readme] to gain a fuller understanding of the features at your disposal.
62188

63-
[Back to main readme][Readme]
189+
⮪ [Back to main Readme][Readme]
64190

65191

66-
[Readme]: https://github.com/ScriptFUSION/Porter/blob/master/README.md
67-
[ECB provider]: https://github.com/Provider/European-Central-Bank
192+
[Readme]: https://github.com/ScriptFUSION/Porter/blob/master/README.md#quick-start
193+
[Steam provider]: https://github.com/Provider/Steam
194+
[Steam 250]: https://steam250.com
195+
[Steam]: https://store.steampowered.com
196+
[ADR]: https://github.com/pmjones/adr
197+
[Amp v3]: https://v3.amphp.org
198+
[Defining a Service Locator]: https://symfony.com/doc/current/service_container/service_subscribers_locators.html#defining-a-service-locator
199+
[Providers]: https://github.com/provider

docs/Quickstart.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ DKK: 7.4469
5858

5959
Since these rates come from the European Central Bank, they're relative to the Euro (EUR), which is assumed to always be *1*. We can use this information to write a currency converter that's always up-to-date with the latest exchange rate information.
6060

61-
This just scratches the surface of Porter without going into any details. Explore the [rest of the manual][Readme] at your leisure to gain a fuller understanding of the features at your disposal.
61+
This just scratches the surface of Porter without going into any details. Explore the [rest of the manual][Readme] to gain a fuller understanding of the features at your disposal.
6262

63-
[Back to main readme][Readme]
63+
[Back to main Readme][Readme]
6464

6565

66-
[Readme]: https://github.com/ScriptFUSION/Porter/blob/master/README.md
66+
[Readme]: https://github.com/ScriptFUSION/Porter/blob/master/README.md#quick-start
6767
[ECB provider]: https://github.com/Provider/European-Central-Bank

0 commit comments

Comments
 (0)