phpcomposer-phpsatis

Mixing wildcard and static version numbers in composer project


I'm having problems at work where we are trying to split our legacy codebase into smaller composer projects to decouple the code and refactor in a controlled way.

We host our code on bitbucket and have a satis repo with "require-all": true and all of our repos listed. This has been working really well.

The problem we are having is when we come to tag new versions of our projects, we have to update all of the other projects which depend on it to point to the exact version number we just tagged. We started to use wildcards to try and alleviate this pain and then just setting a specific version number in our 'core' application, however, we get errors like could not be found in any version even though if we change back to have all the version numbers statically set the same, it works fine.

Project A composer.json

...
"require": {
    "doctrine/dbal": "2.4"
}
...

Project B composer.json

...
"require": {
    "acmeco/project_a": "1.0.*"
},
...

Project C composer.json

...
"minimum-stability": "stable",
"require": {
    "acmeco/project_a": "1.0.4"
    "acmeco/project_b": "1.0.9"
}
...

When running composer update we get the following:

Your requirements could not be resolved to an installable set of packages.

Problem 1
  - The requested package acmeco/project_a could not be found in any version, there may be a typo in the package name.
Problem 2
  - acmeco/project_b 1.0.9 requires acmeco/project_a 1.0.4 -> no matching package found.
  - acmeco/project_b 1.0.9 requires acmeco/project_a 1.0.* -> no matching package found.
  - Installation request for acmeco/project_b 1.0.9 -> satisfiable by acmeco/project_b[1.0.9].

If I change Project C's composer.json to use 1.0.* or Project B's composer to use 1.0.4, all is right with the world.

Maybe I'm not using composer as intended but I'd have thought it would see that Project B just wants 1.0.* of project_a and Project C wants the specific 1.0.4 so it should just go ahead and install project_a 1.0.4 because everyone is happy with that.

Any help/suggestions is much appreciated.


Solution

  • First of all: Stick to semantic versioning. Always evaluate if the new version does only fix something, adds a new feature or breaks backward compatibility, and tag it accordingly. And if you unintentionally didn't correctly increment the version, you should treat it as a bug, revert the change and release it as a bugfix. You can then release the change with a higher level of version increment.

    If you get the semantic versions right, you can then change to use the tilde operator to specify versions. I am a huge fan of them, and they work especially well with semantic versions.

    This would mean that in your project C you would

    "require": {
        "acmeco/project_a": "~1.0",
        "acmeco/project_b": "~1.0"
    }
    

    And in project B also:

    "require": {
        "acmeco/project_a": "~1.0"
    }
    

    And in project A (depending on whether "doctrine/dbal" promises to implement semantic versions - if not: 2.4.*):

    "require": {
        "doctrine/dbal": "~2.4"
    }
    

    And then you should explain why project C has a direct dependency on project A. It might be entirely possible that it has, but IF it has it because Code in project C uses classes of project A, then project B should also be questioned. However, it's more likely that project C shouldn't have a direct dependency on package A, and the only dependency is within package B.

    But on the other hand, by allowing every package to require ANY version that promises to be compatible, but might contain bugfixes and new features, you give Composer the needed slack to get the version requirements right.

    To better visualize the version dependencies I highly suggest you use graph-composer. This will reveal which version requirements are contained in the packages.

    Is there a reason you think it's necessary to pinpoint the exact version in the package C? Composer already records the exact version you fetched when updating and will always install that exact version when installing (you have to commit the composer.lock file).

    There would be only one need to change the version requirements in the composer.json file: If you know you need a new feature only available with a new (compatible or incompatible) version of your packages, you should explicitly change the version requirement. For incompatible changes this is required - otherwise you won't get the update. For compatible changes you may still change it, even though you'd automatically get the newer version, just in case another package might force the downgrade later.