Categories
Software Development Web Development

How to update Symfony minor versions

This is a work in progress. I’ll keep updating it with time.  Most of my articles are for me so I can remember what I did and the problems I had and how I solved them.

I’d suggest updating your composer packages more often than just when you are moving to a newer version of Symfony. That alone is the most time consuming part. If you keep your packages updated it is less work when you need to update to minor/major new versions.

Read the documentation about this subject first.  This is a walk through or an overview with a little more information than the docs, along with some jokes.

First you need to  know some things about composer, so  read the docs, watch some videos, do some googling, learn more and refresh your brain.

Next you’ll need to be fairly knowledgeable of .git and it’s many commands and how it functions. Read that link to the short little book that quickly explains most of it, you only really need like the first 3 -4 chapters fresh in your mind.

yoda practice meme
Yes you must.

Unless your app is tiny an simple, I suggest you practice updating your app first and take notes so that you can have a smoother flow when you update your actual project. For example if you try to update your composer packages all to the latest, you will find conflicts and other issues, some will need to be downgrade some etc.

For me it is a lot less stressful to clone my app in a different directory and quickly try the process and take notes and make sure not to push back to the main repo… unless I just get lucky and shit magically works 100%. I messed up a lot during this process the first time. I had not fully updated my app and some things were left all the way from version like 3.4?

It depends on how old your app is aka Symfony version. How well it has been kept updated and much more, like what bundles you use etc.. This can take many hours depending on the size of your project, how much you know about git, how much you know about composer and how good your google skills are.

Doing Research!!!

You will want to check the change log. This Symfony article mentions how to find that information in the changelog. This symfonycast article has some more good information about Symfony recipes.

Most of all look closely at the output and read what it tells you. I’d recommend you spend a day or two messing around unless your app is tiny.

If you are upgrading from 5.* to 5.4 then here is a long list of things you need to consider. change log

You can either create a new directory and pull your project and play around first or you can also branch your code.   You will need to commit every time you run composer recipes:update or it won’t let you run it again. This means you could be making lots of commits and later diffing and merging and resolving conflicts. This is why I said you will need to be fairly good with GIT, just read the first few chapters of the book.

You might want to refresh your mind of the Symfony configurations because some of it will be changes to configs, some have changed and some may be different than what your current version uses. Make sure you choose the right version of Symfony to match yours in the left top corner when reading the docs.

symfony docs version example
Choose the version that matches the one you are upgrading to.

Step 1ish Updating composer.json

Here is my composer.json for example

{
    "type": "project",
    "name": "sogi/sogi",
    "description": "A total waste of my time",
    "license": "proprietary",
    "require": {
        "php": "^8.1",
        "ext-ctype": "*",
        "ext-fileinfo": "*",
        "ext-gd": "*",
        "ext-iconv": "*",
        "ext-json": "*",
        "ext-memcached": "*",
        "ext-pdo": "*",
        "ext-redis": "*",
        "aws/aws-sdk-php": "^3.209",
        "cocur/slugify": "^4.0",
        "composer/package-versions-deprecated": "^1.10",
        "doctrine/annotations": "^1.13.2",
        "doctrine/common": "^3.2",
        "doctrine/doctrine-bundle": "^2.5.5",
        "doctrine/doctrine-migrations-bundle": "^3.2",
        "doctrine/orm": "^2.11.1",
        "friendsofsymfony/jsrouting-bundle": "^2.7",
        "phpdocumentor/reflection-docblock": "^5.2",
        "ramsey/uuid": "^4.2",
        "ramsey/uuid-doctrine": "^1.7",
        "sensio/framework-extra-bundle": "^5.1",
        "symfony/asset": "5.4.*",
        "symfony/config": "5.4.*",
        "symfony/console": "5.4.*",
        "symfony/dotenv": "5.4.*",
        "symfony/event-dispatcher": "5.4.*",
        "symfony/expression-language": "5.4.*",
        "symfony/flex": "^1.3.1",
        "symfony/form": "5.4.*",
        "symfony/framework-bundle": "5.4.*",
        "symfony/http-client": "5.4.*",
        "symfony/intl": "5.4.*",
        "symfony/mailer": "5.4.*",
        "symfony/messenger": "5.4.*",
        "symfony/mime": "5.4.*",
        "symfony/monolog-bundle": "^3.1",
        "symfony/notifier": "5.4.*",
        "symfony/process": "5.4.*",
        "symfony/property-access": "5.4.*",
        "symfony/property-info": "5.4.*",
        "symfony/proxy-manager-bridge": "5.4.*",
        "symfony/runtime": "5.4.*",
        "symfony/security-bundle": "5.4.*",
        "symfony/security-csrf": "5.4.*",
        "symfony/security-http": "5.4.*",
        "symfony/serializer": "5.4.*",
        "symfony/string": "5.4.*",
        "symfony/translation": "5.4.*",
        "symfony/translation-contracts": "2.3.*",
        "symfony/twig-bundle": "5.4.*",
        "symfony/validator": "5.4.*",
        "symfony/web-link": "5.4.*",
        "symfony/webpack-encore-bundle": "^1.13.2",
        "symfony/yaml": "5.4.*",
        "symfonycasts/reset-password-bundle": "^1.1",
        "twig/extra-bundle": "3.2.1",
        "twig/twig": "3.2.1"
    },
    "require-dev": {
        "dama/doctrine-test-bundle": "^6.3",
        "doctrine/doctrine-fixtures-bundle": "^3.4.1",
        "symfony/browser-kit": "5.4.*",
        "symfony/css-selector": "5.2.*",
        "symfony/debug-bundle": "^5.2",
        "symfony/maker-bundle": "^v1.28.0",
        "symfony/phpunit-bridge": "^5.2",
        "symfony/stopwatch": "^5.2",
        "symfony/var-dumper": "5.2.*",
        "symfony/web-profiler-bundle": "^5.2"
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": {
            "*": "dist"
        },
        "sort-packages": true,
        "allow-plugins": {
            "symfony/flex": true,
            "symfony/runtime": true
        }
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },
    "replace": {
        "paragonie/random_compat": "2.*",
        "symfony/polyfill-ctype": "*",
        "symfony/polyfill-iconv": "*",
        "symfony/polyfill-php72": "*",
        "symfony/polyfill-php71": "*",
        "symfony/polyfill-php70": "*",
        "symfony/polyfill-php56": "*"
    },
    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
        },
        "post-install-cmd": [
            "@auto-scripts"
        ],
        "post-update-cmd": [
            "@auto-scripts"
        ]
    },
    "conflict": {
        "symfony/symfony": "*"
    },
    "extra": {
        "symfony": {
            "allow-contrib": true,
            "require": "5.4.*"
        }
    }
}

Each line you see above in the require sections, may need to be updated.

cat that is a lot meme
You could have more lines than this depending on your project size and complexity

This to me is one of the most complicated and time consuming parts. This is where the printed composer.json file comes in handy to let you see what all you are using and take notes next to each one when you  go to packagist to look them up and find the latest versions of each. One problem you will find when updating is conflicts, you will need to downgrade some things in order to work with others. So you make a change to the latest package and then later another bundle you use needs an older version and so composer downgrades it.

This is only the first part of the battle.

gi joe half the battle meme
Now you know!!!

Here is the output I got after updating the package/bundle versions and using composer update, after running composer recipes:update. Basically I ran the composer update command after running the composer recipes:update command, a little out of order.

You can see some things were downgraded.

Below is the packagist page for twig, notice it is v3.3.8 that is the number you need if you want to update to the latest version.

Packagist for twig

You need to do that for each package you want updated. For Symfony it is mostly easy, as you can change minor versions from like 5.1 to 5.4 and run composer update. For others you need to write the version down and add it to composer and then run update, read the output and follow it.

I also suggest running composer update after every change. Don’t make the mistake I once made and hop right along and change a bunch then try to update. OH HELL COMES UPON YOU IF YOU DO THAT.  You will have mistakes, conflicts to resolve etc. It is much easier to make one change, update, fix/change and move to the next.

For many of these composer packages you will need to read the output carefully and possible update dependencies. That is one of the main reasons I say go slow one at a time.

The Log of Changes

holy change log batman meme
Read the changes carefully

You will need to view the change log changes and hunt for possible places in your code where code changes need to be made. Like I said from just version 5.3 to 5.4 there is a lot. You may have to dig even further back if your version is older like 5.1 or 5.2ish etc. That is what I’ve been doing, some of my bundles were ancient.

This part is sort of painful and slow too. What I did was check the deprecation’s listed in app profiler bar, make one change check it again… What I found to help was to use my IDE PHPStorm’s find and replace feature to find all the places where object xxx was used and replace it with yyy. Some of the notices might make you say WTF and scratch your head.

Uhm, wait… what?

And you must google and search and figure it out. It was not fun. It required many curse words.

Composer Recipes

This is the next step.

This is something I’ve just heard of. I am figuring this  out currently and will update this section as I figure it out, ruin some shit, take screenshots etc. Here is the Symfony release article about this feature with more info.
But this so far is how I’ve destroyed my app(the local copy)

DO NOT RUN THE WEBPACK ENCORE UPDATE, it destroys your F***ing encore configuration by replacing it.

Remember these updates are not interactive. They don’t show you what they are going to do ahead of time and ask you if it is ok.  They don’t ask you if you’d like to do xyz. They just do shit, tell you there was a conflict and BOOM it is up to you to find the files and fix them. You use git status or other commands to see what was changed, where etc. Then you open the file and delete the parts you don’t want. Maybe there is a better way, but this is how I did it. The ours>>> === theirs >>> didn’t make much sense to me, it didn’t seem to follow the way it should work in several files, some were better than others.  It was hard to see what changes happened in the file. The longer the file, the harder it was to discern the changes. Like I said I WRECKED some shit the first few times.

The only way I can figure to update Webpack is to rename the file that gets destroyed then copy/cut/replace with my working code again. This is the reason I said to practices before you do a ANYTHING. The first time around you are bound to WRECK YOUR APP. My first time I lost all kinds of configuration etc. I totally wrecked my app locally and had to delete the whole damn folder and clone my repo and start again. The second time I did a little better and that is when I decided to write this article. The third time as the charm… except for Webpack.

Here is a short video demonstrating some of the coolness of composer recipes:update, it works pretty good, but you got to play with it and learn WTF is going on first. LOL

 

I need to capture one with conflicts and show that next. I’ll pull from an older commit later and redo the process and capture more. I screwed up  the version in the video by blindly updating Webpack, it fubared my precious config.

Categories
Software Development Web Development

How to set application values for easy reuse in Symfony 5+

This article is a work in progress and will be further updated much more in the future. I don’t feel it is complete yet, I’d like to cover more details, more screenshots etc..

So how do you set/configure a value like an upload directory in one location in Symfony so that you can easily use it in your app later? It is actually easier than it even sounds.

Symfony has this handy system you can use to set parameters once and fetch them later. Here in this SymfonyCast under the section about moving an uploaded file, you will see this line.

$destination = $this->getParameter('kernel.project_dir').'/public/uploads';

Forget a moment about the full string. $this->getParameter() is what I want to focus on. The Symfony documentation metions accessing configuration parameters. And that $this->getParameters() is how you get to them.

The docs go further and say this is how you access them in the controller. To get them in your services you should autowire them and if you need them in services often you should bind them. I’ll add that below.

If you mostly need parameters in controllers, setting them as parameters like below is easy. If you need them in multiple services, then binding them is easier.

You can set your own parameters in the file /app/config/services.yaml under a section named… “Parameters”  or “bind” them globally under the _defaults section.

Mostly controllers

Lets look at how to do that really quickly. Here is what is in the top portion of my file.

# /app/config/services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
  app.db_user: '%env(DATABASE_USER)%'
  app.db_pass: '%env(DATABASE_PASSWORD)%'
  app.user_uploads_dir: '%kernel.project_dir%/uploads'
services:
  # default configuration for services in *this* file
  _defaults:
    autowire: true      # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

The whole file is really long I just wanted enough to show a small section so you could identify it visually. You can find more about the configuration parameters in the docs here.

See the parameters section. There are three parameters I have set. app.user_upload_dir: ‘%kernel.project_dir%/uploads’ is one I made up for my own use. With that syntax I don’t have to add anything to the end of it like in the SymfonyCast code above.

I think it shows this type change later in the SymfonyCast too. You should read that full SymfonyCast if you are interested in learning how to work with uploads in Symfony 5+ Here is another great SymfonyCast all about configuration in Symfony 5+ I highly suggest you read it.

But anyways like the docs show, if I need the value of the uploads directory inside a controller, I simply access it like this.

$uploadsDir = $this->getParameter('app.user_uploads_dir');

And I don’t have to worry about misspelling it, and it can be changed in one location now if it ever needs to be changed.

Here is another link from the Symfony documentation about configuration overall.

Using Bind

Using bind seems to work more easily in more places. I am not sure of any disadvantages, the main advantage seems to be ease of use. The other methods require you to autowire some other service and call some specific method. My memory sucks, so this is the easier way for me . LOL

The documentation on this subject isn’t great, it quickly glosses over the subject.

The best info I could find on bind and how it works is actually in a SymfonyCast like usual.

Below is what  I have set for example.

# default configuration for services in *this* file
_defaults:
  autowire: true      # Automatically injects dependencies in your services.
  autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
  bind:
    # autowire this variable name to get the value below
    $userUploads: '%kernel.project_dir%/uploads'

Now to use the value of $userUploads all I do is add it to the Controller route definition as a parameter or add it to the __construct() of a Service class aka any other class.

Example uses

For a Controller router method.

public function save_user_image(Request $request, Security $security,
                                MessageBusInterface $messageBus, $userUploads): JsonResponse

For a Service constructor.

public function __construct(LoggerInterface $exceptionsLogger, $userUploads)
    {
        $this->exceptionLogger = $exceptionsLogger;
        $this->userUploadsDir = $userUploads . '/';
    }

How do you view the parameters?

If you are using bind, you simply look at what you have defined in the services.yaml file.
To see what parameters you can access use the following command. Only values you have defined under the “parameters” section of the services.yaml file and the parameters that symfony sets internally are visible with this command.

php bin/console debug:container --parameters

That will output a really long list of all of the parameters and info about them like this.

symfony output parameters
A list of the Symfony parameters you can use

There are literally hundreds of them. So many I am willing to bet if you need to use something or know where it is, this will show you.