Visitar URL original
Porter/docs/Quickstart Symfony.md at master · ScriptFUSION/Porter · GitHub
Skip to content

Latest commit

 

History

History
211 lines (155 loc) · 8.85 KB

Quickstart Symfony.md

File metadata and controls

211 lines (155 loc) · 8.85 KB

Porter Quick Start Guide for Symfony

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 you want to integrate Porter into an existing Symfony project, skip all steps except modifying services.yaml.

This steps in this guide are automatically verified by a nightly CI build to ensure their correctness. However, if you encounter any errors or get stuck, don't hesitate to file an issue.

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.

composer create-project symfony/skeleton . ^5

Let's start with the Steam provider for Porter by including it in our composer.json with the following command.

Note: The Steam provider requires Amp v3, which is currently in beta, so we need to allow beta dependencies temporarily. This can be enabled with the following commands.

composer config minimum-stability beta
composer config prefer-stable true
composer require --with-dependencies provider/steam

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.

Now the provider is installed along with all its dependencies, including Amp and Porter herself.

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.

Note: We're following the ADR pattern rather than MVC, so we should rename the Controller directory to Action, too, but to keep things simple we will refrain for now.

Actions only handle a single route, using the __invoke method, so let's add that, too.

<?php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

final class AppListAction extends AbstractController
{
    public function __invoke()
    {
    }
}

Let's make our new action act as the home page for our application by making it respond to the / route path.

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Routing\Annotation\Route;

+    #[Route('/')]
     public function __invoke()

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.

composer require doctrine/annotations ^1

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.

 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
 use Symfony\Component\Routing\Annotation\Route;

-    public function __invoke()
+    public function __invoke(): Response
     {
+        return new StreamedResponse(
+            fn () => print 'Hello, Porter!',
+        );
     }

Start a web server to view the home page.

php -S localhost:80 public/index.php

We should see Hello, Porter! displayed in our browser. If you see the default Symfony welcome page, ensure doctrine/annotations was installed correctly.

To gain access to the Steam data we need to inject Porter into our action.

+use ScriptFUSION\Porter\Porter;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

-    public function __invoke(): Response
+    public function __invoke(Porter $porter): Response

Refreshing our browser now will display an error because we haven't told Symfony how to configure Porter as a service yet:

RuntimeException Cannot autowire argument $porter of "App\Controller\AppListAction()": it references class "ScriptFUSION\Porter\Porter" but no such service exists.

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.

    ScriptFUSION\Porter\Porter:
      arguments:
        - '@providers'

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.

    providers:
        class: Symfony\Component\DependencyInjection\ServiceLocator
        arguments:
            -
                - '@ScriptFUSION\Porter\Provider\Steam\SteamProvider'

Note: We do not use the !service_locator shortcut to implicitly create the service locator due to a Symfony bug that misnames services added in this way.

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.

    ScriptFUSION\Porter\Provider\Steam\SteamProvider: ~

Note: The tilde is optional and can be omitted.

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!

Let's replace the previous StreamedResponse closure with a new implementation that uses Porter to import data from the GetAppList resource (a resource belonging to SteamProvider).

+use ScriptFUSION\Porter\Import\Import;
 use ScriptFUSION\Porter\Porter;
+use ScriptFUSION\Porter\Provider\Steam\Resource\GetAppList;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

-            fn () => print 'Hello, Porter!',
+            function () use ($porter): void {
+                foreach ($porter->import(new Import(new GetAppList())) as $app) {
+                    echo "$app[appid]\n";
+                }
+            },

We iterate over each result from GetAppList and emit it using echo, appending a new line to each. 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 complete code, including the new content-type header:

<?php
declare(strict_types=1);

namespace App\Controller;

use ScriptFUSION\Porter\Import\Import;
use ScriptFUSION\Porter\Porter;
use ScriptFUSION\Porter\Provider\Steam\Resource\GetAppList;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Annotation\Route;

final class AppListAction extends AbstractController
{
    #[Route('/')]
    public function __invoke(Porter $porter): Response
    {
        return new StreamedResponse(
            function () use ($porter): void {
                foreach ($porter->import(new Import(new GetAppList())) as $app) {
                    echo "$app[appid]\n";
                }
            },
            headers: ['content-type' => 'text/plain'],
        );
    }
}

This should output a long list of numbers, one on each line, in the browser. For example:

2170321
1825161
1897482
2112761
1829051
...

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.

This just scratches the surface of Porter without going into any details. Explore the rest of the manual to gain a fuller understanding of the features at your disposal. For another worked example, check out the framework-less quickstart guide, too.

⮪ Back to main Readme