{"id":15529044,"url":"https://github.com/alexander-schranz/composer-optional-dependencies","last_synced_at":"2025-03-28T21:43:26.408Z","repository":{"id":218056498,"uuid":"619952884","full_name":"alexander-schranz/composer-optional-dependencies","owner":"alexander-schranz","description":"An article about how to handle optional dependencies with composer.","archived":false,"fork":false,"pushed_at":"2024-01-19T12:36:13.000Z","size":12,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-03T08:13:36.906Z","etag":null,"topics":["alexander-schranz-article","composer","optional-dependencies"],"latest_commit_sha":null,"homepage":"","language":null,"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/alexander-schranz.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}},"created_at":"2023-03-27T18:34:21.000Z","updated_at":"2025-01-07T15:53:16.000Z","dependencies_parsed_at":"2024-01-19T13:48:40.795Z","dependency_job_id":"3d09a4f9-c4d5-41a7-8631-8435310737e8","html_url":"https://github.com/alexander-schranz/composer-optional-dependencies","commit_stats":null,"previous_names":["alexander-schranz/composer-optional-dependencies"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexander-schranz%2Fcomposer-optional-dependencies","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexander-schranz%2Fcomposer-optional-dependencies/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexander-schranz%2Fcomposer-optional-dependencies/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexander-schranz%2Fcomposer-optional-dependencies/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexander-schranz","download_url":"https://codeload.github.com/alexander-schranz/composer-optional-dependencies/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246106668,"owners_count":20724400,"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","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":["alexander-schranz-article","composer","optional-dependencies"],"created_at":"2024-10-02T11:16:04.235Z","updated_at":"2025-03-28T21:43:26.388Z","avatar_url":"https://github.com/alexander-schranz.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Handling optional dependencies with composer\n\nOptional dependencies is something you normally should avoid in any case of\na library where it is possible. In this case we first look at alternatives\nand then how we still could work with optional dependencies.\n\n## Why are optional dependencies bad?\n\nThe main reason why optional dependencies are bad is that you can not be sure\nthat the dependency which is installed by the project matches the version which\nyour library is compatible with.\n\nIt is common that if you work with `optional dependencies` already that you did define\nthe requirement of your `optional dependency` in the `require-dev` section. But as example\nif you have:\n\n```json\n{\n    \"require-dev\": {\n        \"elasticsearch/elasticsearch\": \"^3.0\"\n    }\n}\n```\n\nA project installing your package will not fail when it has in its composer.json:\n\n```json\n{\n    \"require\": {\n        \"elasticsearch/elasticsearch\": \"^4.0\"\n    }\n}\n```\n\nAnother good example about optional dependencies is for example the [`doctrine/orm`](https://github.com/doctrine/orm/).\nWhen you install the `doctrine/orm` package the installation will not fail when\nyou don't have a maybe required `pdo`, `pgsql` or `mysql` extension not installed.\nThat is about the nature how doctrine/orm is build as a whole package with different\ndrivers in it, and so they can not define the `require` section as different drivers\nrequire different things. So it does not make sense in `doctrine/orm` to require all\nused extensions as only one driver is used mostly inside a project.\n\nThe alternative here to `optional` dependencies would be to split a package\ninto multiple packages via as example\n[Adapter Pattern](https://en.wikipedia.org/wiki/Adapter_pattern).\nA good example for this is the [`league/flysystem`](http://github.com/league/flysystem) package. That package\nprovides different adapters which have their own `requirements` and so exactly\ntells you with which already installed packages it is compatible or not. As an example\nit provides a `league/flysystem-aws-s3-v3` adapter which requires a specific version of\n`aws/aws-sdk-php` and as that is an own package it make sure that you have the correct version of\n`aws/aws-sdk-php` installed.\n\n## What does composer say about optional dependencies?\n\nIn the official documentation you will not find any statement about `optional dependencies`.\nBut if you search in the issues of composer you will find a [`RFC` issue for `optional dependencies`](https://github.com/composer/composer/issues/8184).\n\nI want to quote here the answer from [Seldaek](https://github.com/composer/composer/issues/8184#issuecomment-501265594)\n\n\u003e [Seldaek](https://github.com/Seldaek) commented on [Jun 12, 2019](https://github.com/composer/composer/issues/8184#issuecomment-501265594)  \n\u003e   \n\u003e Monolog is a very good example of doing it wrong really..  \n\u003e ElasticSearchHandler should be published as a standalone package with a requirement on both monolog and elasticsearch/elasticsearch, so the version can be enforced. That is why I don't accept any new handlers anymore with external requirements.\n\u003e   \n\u003e Adding optional dependencies are not worth the trouble IMO in composer.\n\nThat was very eye-opening for me why in general optional dependencies are wrong and that\nkind of things should be split into own packages where possible.\n\n## The composer `provide` feature\n\nComposer supports something which is called `virtual packages`.\nA good example for this is the `php-http/client-implementation` package:\n\n```json\n{\n    \"require\": {\n        \"php-http/client-implementation\": \"^1.0\"\n    }\n}\n```\n\nThis package is a virtual package and will require a package which provides the\n`php-http/client-implementation` implementation. And the end user of your package then\nis required to install a package which requires this implementation. A package doing\nthis tells packagist/composer this via:\n\n```json\n{\n    \"provide\": {\n        \"php-http/client-implementation\": \"1.0\"\n    }\n}\n```\n\nTo recommend a specific implementation you can use the `suggest` section:\n\n```json\n{\n    \"suggest\": {\n        \"symfony/http-client\": \"For using Symfony as HTTP client\"\n    }\n}\n```\n\nIn case of the `php-http/client-implementation` the `php-http/discovery` package\nexist which is since `1.15` also a composer plugin which automatically installs\nthe best matching library and even don't need to handle yourself. Thanks goes here\nto [nicolas-greaks](https://github.com/php-http/discovery/pull/208) providing this\ngreat feature.\n\nFor optional packages you could also use the `provide` section to create your own\nvirtual packages and provide packages for it.\n\nIf you want avoid accidentally created optional dependencies you should have a look at\n[maglnet/ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker) plugin.\n\n## How to handle optional dependencies with composer\n\nThere are maybe situation where you just don't want to split your package up. There\ncould be different reasons for this that one package with optional packages provides\na better developer experience as that the enduser of your library need to install \nseveral packages.\n\nIn my situation while working on the [`schranz-search/schranz-search`](https://github.com/schranz-search/schranz-search) packages. I did\ngo the `flysystem` way and using the\n[Adapter Pattern](https://en.wikipedia.org/wiki/Adapter_pattern) to split the support\nof different search engines into different packages. So good so fine no optional\ndependencies needed as every package itself requires the needed dependencies.\n\nBut now I did come to the situation about integrating the different packages into\ndifferent Frameworks. In my example I wanted to create a `Symfony Bundle` which\nintegrates the library into Symfony Ecoystem. If I would be strict about optional\ndependencies here I would have needed to create for every adapter an own bundle.\nAlso I did not wanted to require all adapters in the `require` section of my bundle\nas that would install a lot of dependencies and maybe even dependencies of dependencies\nbetween adapters are not even compatible with each other.\n\nAs creating multiple bundles per adapters and I want to go an easy replaceable `DSN`\nway of configuring the bundle and make so changing between adapters easy. I needed\nsome kind of optional dependencies.\n\nSo to test my bundle against all adapters I did add them as `require-dev` to my\n`composer.json`. But as mention above this will not avoid that somebody is in future\ninstalling a wrong version of an adapter which is maybe not compatible with the\ninstalled bundle version. So to fix that issue we also add the `conflict` section to\nour `composer.json`:\n\n```json\n{\n    \"require-dev\": {\n        \"schranz-search/seal-algolia-adapter\": \"^0.3\",\n        \"schranz-search/seal-elasticsearch-adapter\": \"^0.3\",\n        \"schranz-search/seal-meilisearch-adapter\": \"^0.3\",\n        \"schranz-search/seal-memory-adapter\": \"^0.3\",\n        \"schranz-search/seal-multi-adapter\": \"^0.3\",\n        \"schranz-search/seal-opensearch-adapter\": \"^0.3\",\n        \"schranz-search/seal-read-write-adapter\": \"^0.3\",\n        \"schranz-search/seal-redisearch-adapter\": \"^0.3\",\n        \"schranz-search/seal-solr-adapter\": \"^0.3\",\n        \"schranz-search/seal-typesense-adapter\": \"^0.3\"\n    },\n    \"conflict\": {\n        \"schranz-search/seal-algolia-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-elasticsearch-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-meilisearch-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-memory-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-multi-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-opensearch-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-read-write-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-redisearch-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-solr-adapter\": \"\u003c0.3 || \u003e=0.4\",\n        \"schranz-search/seal-typesense-adapter\": \"\u003c0.3 || \u003e=0.4\"\n    }\n}\n```\n\nThis way even `composer` is not supporting directly optional dependencies, we can\nover the `conflict` section of `composer.json` make sure that only compatible versions\nof our optional requirements are installed. A good website to find the correct constraint for your `conflict` part is [https://semver.madewithlove.com/](https://semver.madewithlove.com/).\n\nOver `suggest` or with  require an `implementation` like in the above section [here](#the-composer-provide-feature),\ncomposer would also tell the enduser which adapters exist or are suggested by us to be used with our integration bundle.\n\nI hope I could help to understand how to avoid optional dependencies and how to handle them with composer.\n\nIf you have any feedback or questions add it to the Twitter Thread [here](https://twitter.com/alex_s_/status/1640429500763611153) or write an issue on [this](https://github.com/alexander-schranz/composer-optional-dependencies/issues/new) Repository.\nIf you liked the article give it a star here or even retweet it :).\n\nInterested in other articles? Go to the [`alexander-schranz-article` topic](https://github.com/topics/alexander-schranz-article).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexander-schranz%2Fcomposer-optional-dependencies","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexander-schranz%2Fcomposer-optional-dependencies","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexander-schranz%2Fcomposer-optional-dependencies/lists"}