{"id":25898432,"url":"https://github.com/upsun/symfonycon-vienna-2024.deployfriday.com","last_synced_at":"2025-11-27T17:08:20.977Z","repository":{"id":266075306,"uuid":"895590555","full_name":"upsun/symfonycon-vienna-2024.deployfriday.com","owner":"upsun","description":"Symfony Demo application for the SymfonyCon Vienna 2024 talk from Augustin and Florent","archived":false,"fork":false,"pushed_at":"2025-10-05T06:42:13.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-05T08:34:57.346Z","etag":null,"topics":["upsun-example"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/upsun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-28T13:48:12.000Z","updated_at":"2025-10-05T06:42:17.000Z","dependencies_parsed_at":"2024-12-02T19:15:47.388Z","dependency_job_id":null,"html_url":"https://github.com/upsun/symfonycon-vienna-2024.deployfriday.com","commit_stats":null,"previous_names":["upsun/symfonycon-vienna-2024.deployfriday.com"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/upsun/symfonycon-vienna-2024.deployfriday.com","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upsun%2Fsymfonycon-vienna-2024.deployfriday.com","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upsun%2Fsymfonycon-vienna-2024.deployfriday.com/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upsun%2Fsymfonycon-vienna-2024.deployfriday.com/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upsun%2Fsymfonycon-vienna-2024.deployfriday.com/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/upsun","download_url":"https://codeload.github.com/upsun/symfonycon-vienna-2024.deployfriday.com/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/upsun%2Fsymfonycon-vienna-2024.deployfriday.com/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27282327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-26T02:00:06.075Z","response_time":193,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["upsun-example"],"created_at":"2025-03-03T00:18:44.905Z","updated_at":"2025-11-27T17:08:20.971Z","avatar_url":"https://github.com/upsun.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e [!CAUTION]\n\u003e ## This project is owned by the Upsun DevRel team. It has been written by Augustin Delaporte and Florent Huck for the SymfonyCon Vienna 2024 and only intended to be used with caution by Upsun customers/community.   \n\u003e This project is not supported by Upsun and does not qualify for Support plans. Use this repository at your own risks, it is provided without guarantee or warranty!\n\n# Flawless collaboration between front and back developers\n\n## Prerequisites\n\n### Deploy the Symfony skeleton on Upsun\n\n```\nsymfony new symfonycon-vienna-2024 --upsun\ncd symfonycon-vienna-2024\nsymfony project:create --title symfonycon-vienna-2024\nsymfony push\n```\n\n### Configure the Symfony app with the speaker list\n\n#### Change default `app` route to `api.{default}`\nEdit your `.upsun/config.yaml` file and change existing `app` routes to `api.{default}`\n```yaml {location='.upsun/config.yaml'}\nroutes:\n    \"https://api.{all}/\": { type: upstream, upstream: \"app:http\", id: api  }\n    \"http://api.{all}/\": { type: redirect, to: \"https://api.{all}/\" }\n```\nThen AC (git Add, Commit) your Upsun config:\n```shell\ngit add .upsun/config.yaml \u0026\u0026 git commit -m \"Upsun config: change app route to api.{default}\"\n```\n\n#### Add few bundles\nIn order to display a list of users on the `app` frontend, we will need to add some bundles:\n```shell\nsymfony composer require doctrine/annotations \\\n  doctrine/doctrine-bundle \\\n  doctrine/doctrine-migrations-bundle \\\n  doctrine/orm nelmio/cors-bundle \\\n  symfony/doctrine-bridge \\\n  symfony/html-sanitizer \\\n  symfony/http-client \\\n  symfony/intl symfony/monolog-bundle \\\n  symfony/security-bundle \\\n  symfony/serializer \\\n  symfony/twig-bundle \\\n  symfony/asset-mapper \\\n  symfony/asset \\\n  symfony/twig-pack\nsymfony composer require --dev doctrine/doctrine-fixtures-bundle symfony/maker-bundle\n```\n\nThen AC your changes:\n```shell\ngit add . \u0026\u0026 git commit -m \"adding required bundles: doctrine, twig, assets, ...\"\n```\n\n#### Create a Speaker Entity\nWe will create a new entity, using [Marker Bundle](https://symfony.com/bundles/SymfonyMakerBundle/current/index.html)\n```shell\nsymfony console make:entity\n```\nAdd these fields:\n* first_name: string(255),\n* last_name: string(255),\n* username: string(255),\n* picture: string(1024), nullable: true\n* city: string(512), nullable: true\n* distance: integer, nullable: true\n\nThen AC your changes:\n```shell\ngit add . \u0026\u0026 git commit -m \"adding Speaker entity\"\n```\n\n#### Create migration files\nTo generate corresponding migration file for the Speaker entity, we need a database.\nThe DoctrineBundle comes up with a Docker container.\nTo start using it, execute the following:\n```shell\ndocker compose up -d\ndocker ps\n```\nFrom the ``docker ps`` command, copy the external port of the `` Container and update variable `DATABASE_URL` with the right port in your `.env` file.\n```shell\nDATABASE_URL=\"postgresql://app:!ChangeMe!@127.0.0.1:57133/app?serverVersion=16\u0026charset=utf8\"\n```\n\nThen generate a migration file and update your local database using it:\n```shell\nsymfony console doctrine:migrations:diff\nsymfony console doctrine:migrations:migrate\n```\n\nThen AC your changes:\n```shell\ngit add migrations \u0026\u0026 git commit -m \"adding migration for Speaker entity\"\n```\n\n#### Configure Upsun ``app`` to use PostgreSQL:16\nUpdate your ``.upsun/config.yaml`` file and add a postgresql service, PHP extension `pdo_sql` and a relationship to your ``app\n```yaml {location='.upsun/config.yaml'}\nservices:\n  database:\n    type: \"postgresql:16\"\n\napplications:\n  app:\n    #...\n    runtime:\n      extensions:\n        # ...\n        - pdo_pgsql\n    #...\n    relationships:\n      database:\n  \n```\nThen AC your changes:\n```shell\ngit add .upsun/config.yaml \u0026\u0026 git commit -m \"configure app to use PostgreSQL\"\n```\n\n#### Add fixtures\nUpdate existing Fixture file, in ``src/DataFixtures/AppFixtures.php`` with the following\n```php\n\u003c?php\n\nnamespace App\\DataFixtures;\n\nuse App\\Entity\\User;\nuse Doctrine\\Bundle\\FixturesBundle\\Fixture;\nuse Doctrine\\Persistence\\ObjectManager;\n\nclass AppFixtures extends Fixture\n{\n    /** @var ObjectManager */\n    private $objectManager;\n\n    public function load(ObjectManager $manager): void\n    {\n        $this-\u003eobjectManager = $manager;\n\n        $this-\u003ecreateUsers();\n\n        $manager-\u003eflush();\n    }\n\n    private function createUsers()\n    {\n        /* [last_name, first_name, username, city, online_picture, distance ] */\n        $users = [\n            ['Huck', 'Florent', 'flovntp', 'Massieux', 'https://avatars.githubusercontent.com/u/1842696?v=4', 915000],\n            ['Delaporte', 'Augustin', 'guguss', 'Lyon', 'https://avatars.githubusercontent.com/u/1927538?v=4', 915001],\n            ['Dunglas', 'Kevin', 'dunglas', 'Lille', 'https://avatars.githubusercontent.com/u/57224?v=4', 998000],\n            ['Potencier', 'Fabien', 'fabpot', 'Moon', 'https://avatars.githubusercontent.com/u/47313?v=4', 356410002],\n            //...  \n        ];\n        \n        foreach($users as $userData) {\n            $speaker = new Speaker();\n            $speaker-\u003esetLastName($userData[0]);\n            $speaker-\u003esetFirstName($userData[1]);\n            $speaker-\u003esetUsername($userData[2]);\n            $speaker-\u003esetCity($userData[3]);\n            $speaker-\u003esetPicture($userData[4]);\n            $speaker-\u003esetDistance($userData[5]);\n            $this-\u003eobjectManager-\u003epersist($speaker);\n        }\n        $this-\u003eobjectManager-\u003eflush();\n    }\n}\n\n```\nThen execute it on your local database:\n```shell\nsymfony console doctrine:fixture:load\n```\n\nThen AC your changes:\n```shell\ngit add src/DataFixtures/AppFixtures.php \u0026\u0026 git commit -m \"adding fixtures for speakers\"\n```\n\n#### Add a basic frontend\nFirst, you need to create a Controller for your homepage, in ``src/Controller/MainController.php``:\n```php\n\u003c?php\n\nnamespace App\\Controller;\n\nuse App\\Repository\\SpeakerRepository;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\n\nclass MainController extends AbstractController\n{\n    #[Route('/', name: 'app_homepage')]\n    public function homepage(SpeakerRepository $speakerRepository)\n    {\n        $allSpeakers = $speakerRepository-\u003efindBy([], ['id' =\u003e 'ASC']);\n        \n        return $this-\u003erender('main/homepage.html.twig', [\n            'speakers' =\u003e  $allSpeakers,\n        ]);\n    }\n}\n```\n\nThen add corresponding ``templates/main/homepage.html.twig``:\n```html\n{% extends 'base.html.twig' %}\n\n{% block body %}\n  \u003cdiv class=\"col-12\"\u003e\n    \u003ch3\u003eList of attendees at the SymfonyCon Vienna 2024\u003c/h3\u003e\n    \u003cdiv class=\"divTable table table-striped table-dark table-borderless table-hover\"\u003e\n      \u003cdiv class=\"divTableHeading\"\u003e\n        \u003cdiv class=\"divTableRow bg-info\"\u003e\n          \u003cdiv class=\"divTableHead\"\u003ePicture\u003c/div\u003e\n          \u003cdiv class=\"divTableHead\"\u003eSpeaker\u003c/div\u003e\n          \u003cdiv class=\"divTableHead\"\u003eCity\u003c/div\u003e\n          \u003cdiv class=\"divTableHead\"\u003eDistance from Vienna\u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n  \n      {% for speaker in speakers %}\n      \u003cdiv class=\"divTableRow\"\u003e\n        \u003cdiv class=\"divTableCell\"\u003e\n          {% if speaker.picture %}\n            \u003cimg style=\"height: 140px\" src=\"{{ speaker.picture }}\"/\u003e\n          {% else %}\n          {# Thanks https://github.com/ozgrozer/100k-faces?tab=readme-ov-file #}\n            \u003cimg style=\"height: 140px\" src=\"https://randomspeaker.me/api/portraits/men/{{ speaker.id }}.jpg\"/\u003e\n          {% endif %}\n        \u003c/div\u003e\n        \u003cdiv class=\"divTableCell\"\u003e\n          {{ speaker.firstname }} {{ speaker.lastname }} ({{ speaker.username }})\n        \u003c/div\u003e\n        \u003cdiv class=\"divTableCell\"\u003e\n          {{ speaker.city ?? '' }}\n        \u003c/div\u003e\n        \u003cdiv class=\"divTableCell\"\u003e\n          {{ (speaker.distance/1000) | number_format }} km\n        \u003c/div\u003e\n      \u003c/div\u003e\n  \n      {% endfor %}\n    \u003c/div\u003e\n  \u003c/div\u003e\n{% endblock %}\n```\nA few styling of it: Modify your `assets/styles/app.css` with the following:\n```css\nbody {\n    background-color: rgb(21, 32, 43);\n    color: #fff;\n}\n\n/* DivTable.com */\n.divTable{\n    border: 1px solid #999999;\n    display: table;\n    width: 100%;\n}\n.divTableRow {\n    display: table-row;\n    padding: 0.75rem;\n}\n.divTableCell, .divTableHead {\n    display: table-cell;\n    padding: 3px 10px;\n}\n.divTableHeading {\n    background-color: #565151;\n    display: table-header-group;\n    font-weight: bold;\n}\n.divTableFoot {\n    background-color: #565151;\n    display: table-footer-group;\n    font-weight: bold;\n}\n.divTableBody {\n    display: table-row-group;\n}\n\n.table-dark.table-striped .divTableRow:nth-of-type(odd) {\n    background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover .divTableRow:hover {\n    background-color: rgba(255, 255, 255, 0.075);\n}\n\n.sightingLink {\n    cursor: pointer;\n}\n\n.table-dark.table-hover .sightingLink.divTableRow:hover .divTableCell {\n    text-decoration: underline;\n}\n```\nThen compile it using Symfony CLI\n```shell\nsymfony console asset-map:compile\n```\n\nAnd test it on your local frontend:\n```shell\nsymfony server:start -d\nsymfony open:local\n```\nYou should see a basic list of all your speakers from the fixtures.\n\nThen, AC your changes:\n```shell\ngit add assets/styles/app.css src/Controller/MainController.php templates/main/homepage.html.twig \u0026\u0026 git commit -m \"adding styled homepage with speaker list\"\nsymfony push\n```\n\n\u003e **Please note**: After first deploy, only your migration files are executed, but speaker table is empty.\n\u003e To load your Speaker fixtures, you can use the following command:\n\u003e ```shell\n\u003e symfony ssh -- php bin/console doctrine:fixture:load -e dev\n\u003e  ```\n\n#### Add JSON (REST) endpoints\n\u003e **Please note**: All the steps below will prepare our Symfony application for decoupling it\n\u003e by exposing as REST endpoints the list of Speakers and the podium list (the 3 speakers the far away from the event)\n\nWe want to expose the Speaker list and the podium list.\nTo do so, we will create a new Controller with this 2 REST routes and create a Speaker Repository function to get the Podium.\nSo, first, create a new Controller ``src/Controller/SpeakerController.php``:\n```php\n\u003c?php\nnamespace App\\Controller;\n\nuse App\\Repository\\SpeakerRepository;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Attribute\\Route;\n\nclass SpeakerRestController extends AbstractController\n{\n    #[Route('/api/get-speaker-list', methods: ['GET'])]\n    public function getSpeakerList(SpeakerRepository $speakerRepository): Response\n    {\n        return $this-\u003ejson($speakerRepository-\u003efindBy([], ['id' =\u003e 'ASC']));\n    }\n\n    #[Route('/api/get-podium', methods: ['GET'])]\n    public function getPodium(SpeakerRepository $speakerRepository): Response\n    {\n        return $this-\u003ejson($speakerRepository-\u003egetSpeakerPodium());\n    }\n}\n```\n\nThen add a new ``getSpeakerPodium`` function in your `src/Repository/SpeakerRepository.php` to fetch all the speakers:\n\n```php\n\u003c?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Speaker;\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository;\nuse Doctrine\\Persistence\\ManagerRegistry;\n\n/** @extends ServiceEntityRepository\u003cSpeaker\u003e */\nclass SpeakerRepository extends ServiceEntityRepository\n{\n    public function __construct(ManagerRegistry $registry)\n    {\n        parent::__construct($registry, Speaker::class);\n    }\n    \n    public function getSpeakerPodium()\n    {\n        return $this-\u003ecreateQueryBuilder('s')\n            -\u003eorderBy('s.distance', 'DESC')\n            -\u003esetMaxResults(3)\n            -\u003egetQuery()\n            -\u003egetArrayResult();\n    }\n}\n```\n\nAnd test the 2 new endpoints [/api/get-speaker-list](https://localhost:8000/api/get-speaker-list) and [/api/get-podium](https://localhost:8000/api/get-podium)\n\nThen, AC your changes:\n```shell\ngit add src/Controller/SpeakerController.php src/Repository/SpeakerRepository.php \u0026\u0026 git commit -m \"adding REST endpoint (Json) for speaker list and podium\"\n```\n\n#### Add sanitization of Upsun preview environments\nIn order to not expose production data to potential external member of your company working on your project (preview environment), we will setup our project to sanitize preview databases on the fly during deploy hook.\n\nFirst create a new command to sanitize data, in ``src/Command/SanitizeDataCommand.php``:\n```php\n\u003c?php\n/* src/Command/SanitizeDataCommand.php */\n\nnamespace App\\Command;\n\nuse App\\Entity\\Speaker;\nuse App\\Repository\\SpeakerRepository;\nuse Doctrine\\ORM\\EntityManagerInterface;\nuse Symfony\\Component\\Console\\Attribute\\AsCommand;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\n\n#[AsCommand(\n    name: 'app:sanitize-data',\n    description: 'Sanitize speaker data (first_name, last_name, username and picture).',\n    aliases: ['app:sanitize']\n)]\nclass SanitizeDataCommand extends Command\n{\n    private SymfonyStyle $io;\n\n    public function __construct(private SpeakerRepository $speakerRepository, private EntityManagerInterface $entityManager)\n    {\n        parent::__construct();\n    }\n\n    protected function initialize(InputInterface $input, OutputInterface $output): void\n    {\n        $this-\u003eio = new SymfonyStyle($input, $output);\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $speakers = $this-\u003espeakerRepository-\u003efindAll();\n        $this-\u003eio-\u003eprogressStart(count($speakers));\n\n        $this-\u003eentityManager-\u003egetConnection()-\u003ebeginTransaction(); // suspend auto-commit\n        try {\n            /** @var Speaker $speaker */\n            foreach ($speakers as $speaker) {\n                $this-\u003eio-\u003eprogressAdvance();\n                // fake user info\n                $speaker-\u003esetLastName('Wick');\n                $speaker-\u003esetFirstName('John');\n                $speaker-\u003esetUsername(uniqid('john-wick-'));\n                $speaker-\u003esetPicture('https://cdna.artstation.com/p/assets/images/images/004/943/296/large/andrey-pankov-neo.jpg?1487365474');\n            }\n            $this-\u003eentityManager-\u003eflush();\n            $this-\u003eentityManager-\u003egetConnection()-\u003ecommit();\n            $this-\u003eio-\u003eprogressFinish();\n        } catch (\\Exception $e) {\n            $this-\u003eentityManager-\u003egetConnection()-\u003erollBack();\n            throw $e;\n        }\n\n        return Command::SUCCESS;\n    }\n}\n```\n\nThen, we need to tell Upsun to execute this Symfony command during the deploy hook.\nModify your ``.upsun/config.yaml`` file and add the following at the end of the existing `hooks.deploy` block:\n```yaml\napplications:\n    app:\n        #...\n        hooks:\n            #...\n            deploy: |\n                set -x -e\n\n                symfony-deploy\n\n                # The sanitization of the database if it's not production\n                if [ \"$PLATFORM_ENVIRONMENT_TYPE\" != production ]; then\n                    php bin/console app:sanitize-data\n                fi\n```\n\u003e **Please note**: in our case, our database is small, and so, sanitizing data during the deploy hook is not a big deal, but if you want so more advance technics, please refer to [this blogpost](https://upsun.com/blog/how-to-sanitize-preview-environment-data/)\n\nFinally, AC your changes and deploy:\n```shell\ngit add src/Command/SanitizeDataCommand.php .upsun/config.yaml \u0026\u0026 git commit -m \"adding automatic sanitization of data on preview envs\"\nsymfony push\n```\n\nReady to start!!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fupsun%2Fsymfonycon-vienna-2024.deployfriday.com","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fupsun%2Fsymfonycon-vienna-2024.deployfriday.com","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fupsun%2Fsymfonycon-vienna-2024.deployfriday.com/lists"}