Best way to handle dependencies between components of a PHP infrastructure using Git and Composer

Background

I am developing a framework in PHP. I started by creating each component separately so that it can be used regardless of the frame.

After creating the four libraries A , B , C, and D :

  • A has no dependencies

  • B and C require A

  • D requires A , B and C

Now I have some problems when releasing a new version of one library, I may have to change the dependencies of others and release new versions for them. For example: new version A means new version B , C and D.

I looked at how other lattices, such as Symfony and Laravel , solved this problem. I found out that they use the subtree function of the Git and replace functions for Composer. It works as follows:

  • Each component is in a read-only repository with its own composer.json

  • Each component may require other components, but do not replace them.

  • The Framework repository uses subtrees to include all components. So you do not need to require them to use the composer. But it must require all its dependencies (since this is no longer handled by Compser).

  • The replace frame of all its components.

I also noticed that

  • The component repository contains only the source code (without unit tests!)

  • Laravel made the Contracts component to preserve all interafces of all components, and each component requires it.

Questions

  • My explanation of how Laravel and Symfony solved the problem correctly?

  • Do I need to remove tests from component repositories and put them in the framework?

  • If so, how can someone who just wants to use one component be sure that he passes the tests, regardless of the fact that the entire infrastructure passes the howl tests?

  • Do I have to make sure that all component dependencies are compatible and require them manually within composer.json ?

  • What is the point of having a component for interfaces? In any case, it is impossible to use it autonomously!

  • Is there a better way to solve this problem?

PS : here are links to A , B , C and D

+5
source share
1 answer

Now I have some problems when releasing a new version of one library, I may have to change the dependencies of others and release new versions for them. For example: new version A means new version B, C and D.

  • You have a multi repo approach.
  • Edit A => new version A => bump version needed for B, C and D.

I think the most important thing is to avoid using dev-master and correctly configure your components correctly once they are stable and ready to exit the dev phase. You can then use the Composers range operators (caret ^ and tilde ~ ) to automatically upgrade to the latest version in a specific range of major.minor version. It helps a lot and you take the tedious manual version of the update from your hands.

  • My explanation of how Laravel and Symfony solved the problem correctly?
  • It is not right. The basic concept of development, publication and consumption of packages work differently, and then to what you described.
  • They use a monolithic style of repo development. This is the only repository containing code for a group of packages. The opposite of mono-repo is the many-repo approach. An alternative is git submodules .
  • All modules / nodes of the framework and core / core are in the same storage! For Laravel, this is https://github.com/laravel/framework/tree/5.4/src/Illuminate
  • Each module / bundle folder contains composer.json , and the framework itself contains composer.json
  • This allows you to "split" the folder modules into offline read-only storage. Using a special git helper, for example. git subsplit publish as Laravel uses https://github.com/laravel/framework/blob/636020a96a082b80fa87eed07d45c74fa7a4ba70/build/illuminate-split-full.sh or splitsh https://github.com/splitsh/lite , as Symfony uses
  • The development is mainly repo.
  • Finally, from the point of view of the user / consumer (in any case, in the composer.json of your CMS / application), you just need a module / package from a read-only offline storage source. This is a lot of repos because your application depends on many repositories.

When you update a dependency using Composer, Composer replaces your packages with a newer version.

  1. Do I need to remove tests from component repositories and put them in the framework?

No. You can also leave the tests in the /moduleA/tests folder and set up your unit test collector.

  1. If so, how can someone who just wants to use one component be sure that he passes the tests, regardless of whether the entire system that passes the tests for howling has passed?

Two things. Test object:

  • (a) a component that is ideally independently tested and
  • (b) a structure that consumes many components and testing functions that rely on functions from several components (e.g., kernel / kernel). You can also break the kernel as soon as it stabilizes and is independently verified. (e.g. your component D)
  1. Do I have to make sure that all dependency components are compatible and require them manually in composer.json format?

Monorepo Developer Perspective: A developer / supporter of a framework can only release a new version when all unit tests of all components and all unit tests of the platform itself pass, right? Then it can start splitting the subtree and automatically update new components.

Application Developer Perspective: Yes. As a user of monoreping components, you simply consume autonomous dependencies (from read-only repositories). This means that you need to maintain the versions of the components that are required in your composer.json manually or automatically.

  1. What is the point of having a component for interfaces? In any case, it is impossible to use it autonomously!

Good question!

  • Perhaps the developers want to do something different and "sort things out"

  • Or they have a bad optimization idea on their minds:

It can be argued that interfaces are only development contracts. When all the components are written against the interfaces, you can just pull the plug on them, after testing and before creating the production release. In other words: you can leave the interface store and start removing the interface when you release it for production.

Exiting the repo interface will lead to fatal errors "X interfaces not found." Then you run the “optimizer run” over the rest of the classes and delete all the lines “implements the interface”. Fewer files to include. Less code to parse. Less I / O. And now I will probably be killed in the comments section by suggesting this :) And no, Laravel or Symfony do not.

  1. Is there a better way to solve this problem?

I suggest doing the following: for a project with <5 components, use multi-repo. If> 5 components, go to monorepo.

In general, there are not many options for solving this problem:

  • git submodules
  • mono repo
  • multi repo

Each approach has pro and con .:

  • Updating git submodules aka git updating the version and version submodule leads to git madness because it will be constantly broken. and git madness leads to the dark side. :)
  • Mono repos are easy to maintain and easy to publish. This gives you easy developer support and multi-threading for the consumer. You can immediately replace / rename / refactor for all modules / components.
  • Many repos are difficult to maintain when you have a large number of components.

See also:

+4
source

Source: https://habr.com/ru/post/1263785/


All Articles