78

So how does the "replace" property work with composer? I have read the composer document but still not understand it. Searching for more info hasn't answered my questions.

When I look at the composer.json file on Laravel/Framework on github. I can't see how replace will work. Can someone explain to me how this works? And what the variable "self.version" will equal to?

Ilyes512
  • 2,627
  • 5
  • 23
  • 27

2 Answers2

118

The Composer documentation gives two basic examples. I'll try to explain:

Lists packages that are replaced by this package. This allows you to fork a package, publish it under a different name with its own version numbers, while packages requiring the original package continue to work with your fork because it replaces the original package.

Suppose your software uses original/library and other/package, which itself also requires original/library.

Now you think that original/library needs to integrate a feature, but the maintainers won't let your suggestion happen in their package. You decide to fork that library under the name better/library, and tag a new release.

Back to your software. Of course it should start using better/library, so you require that instead, but that other/package still requires the original/library - code duplication! How can you make that other package to use your better/library instead without also forking it and only changing the composer.json (you are still compatible to that original/library, so it should work)?

You add a replace key to your composer.json:

"replace": {
    "original/library":"1.0.2"
}

Now Composer knows that any package from your better/library is equally good as the original/library when it comes to resolving the dependencies of the other/package.

This is also useful for packages that contain sub-packages, for example the main symfony/symfony package contains all the Symfony Components which are also available as individual packages. If you require the main package it will automatically fulfill any requirement of one of the individual components, since it replaces them.

The same rules, a slightly different angle: Requiring components of a framework is a good approach for any other component that needs some feature. But if you require the full framework in your software, and another library, which subsequently also requires a component of that framework, the replace declaration of the framework allows Composer to not have to install that single component twice, because it is already included in the full framework.

Beware: Placeholders in replaced versions are usually bad

In my original answer I suggested:

"replace": {
    "original/library":"1.*"
}

This has consequences: Composer will now treat your library version 1.0.0 to be as good as ANY version 1.x of the original library, even if they fix stuff or add features and release version 1.2.34 some day. This also means that if your other/package some day gets an update and requires original/library:^1.1, the replacement in YOUR library is still active and states that it can replace ANY version 1.*, even without you updating anything inside - which it can not, your old code will never implement new features of the original library without you doing some work, but the replacement states exactly this.

So in essence: Avoid wildcard versions in the replacement version! If you use them, you make a statement about the future that you cannot know or predict (unless you can control original/library, but even then be very careful). Always use a specific version of the original/library that you know and can re-implement completely.

Sven
  • 69,403
  • 10
  • 107
  • 109
  • 3
    I think I almost got it. So what I didn't understand was how does composer know what to replace (package a with b or package c with d), but composer looks at the class name to compare it, is that right? So any package with the name a/b would get replaced by x/b or y/b. And adding self.version would probably mean only replace when the version numbers are equal. – Ilyes512 Sep 20 '13 at 09:15
  • 1
    Composer only checks the package names in their respective `composer.json` files. It does not check the class names. For the trick to fully work, you have to provide the compatibility in your package that replaces another package (case 1). Composer will still replace your `better/library` with the `original/library`, but if there are classes used in the original that do not have a working equivalent in the better version, code will fail. – Sven Sep 23 '13 at 16:18
  • 1
    I encountered another case (similar to the second example about sub-packages), which is to avoid requiring some polyfills if you know they are not needed: https://github.com/voku/portable-utf8#install-portable-utf-8-via-composer-require "If your project do not need some of the Symfony polyfills please use the replace section of your composer.json. This removes any overhead from these polyfills as they are no longer part of your project." – Mathieu Rey Jun 12 '19 at 08:25
  • @Sven, What if both the `original/library` & `better/library` replaces each other with a `*`? Can I force it to use one of them? – Pini Nov 07 '19 at 09:24
  • @Pini: Circular dependencies are always a very bad idea, I cannot imagine a useful use case for such a scenario. – Sven Nov 07 '19 at 15:07
  • @Sven, I know, but here's the situation: I'm working on a package that is part of a big app (it's included in it, but I have no access to their code). When you install the big app, you'll get the package from their repository & sometimes there's a newer version on my repository, so I want to allow developers that wants to upgrade to use that instead. The thing is, I noticed that they added a `"replace":{"our-package":"*"}` to the version that they're using so my replace doesn't work. Any idea? – Pini Nov 07 '19 at 16:02
13

When you create your own package, you define in your composer.json what kind of packages does it provide which basically tells Composer that your package has it already installed, so no need to install it again.

If you use replace property, it tells Composer that your package wants to replace the original package with your own fork, so other packages don't need to install it.

For example if a/a package requires b/b and you tell to replace b/b, it won't be downloaded on Composer install/update.

This is explained in more details in here: How does the “replace” property work in Composer?

How does the “replace” property work in Composer - diagram

kenorb
  • 155,785
  • 88
  • 678
  • 743