Create and publish a PHP composer package

As you might know, packages or libraries came to make developers life’s easier where the package is a piece of reusable code that can be dropped into any application and be used without any tinkering to add functionality to that code.
So Today, we will introduce the right way to create and publish a PHP composer package step by step.

Pre-requirements:

  • Composer.
  • Project package code.
  • Github Account.
  • Packagist Account.

What is Composer:

  • Composer is a dependency manager tool for PHP packages or libraries.
  • It’s inspired by npm (Node.js) and bundler (Ruby).
  • It uses its official repository packages (packagist.org) to know where the code of package is located which is often in Github (public repository).

Prepare the Package:

Let’s pretend that we have a project structure like this and we would like to make it as a package.

sfmok ~/my-package> tree
.
├── src
│   └── HelloWorld.php
└── tests
    └── HelloWorldTest.php

I know that is a silly package idea but the way to share it it’s the same whatever the kind and/or size of the package.

So let’s begin, the first step is to keep composer generating the config package file composer.json, so move over to the project package path via terminal and run:

composer init

The command has a sprinkling of interfaction questions and we need to treat them carefully.

Initialize Package Configuration
  1. Make sure that your package name follows this convention “<vendor>/<name>” where:
    • <vendor> is often the author name that should be unique (e.g. the username on Github : sfmok)
    • <name> is the name of the package itself e.g: hello-world.
  2. Package Type:
    • library: This is the default. It will copy the files to vendor.
    • project: This denotes a project rather than a library. For example application shells like the Symfony standard edition, CMSs like the SilverStripe installer or full fledged applications distributed as packages. This can for example be used by IDEs to provide listings of projects to initialize when creating a new workspace.
    • metapackage: An empty package that contains requirements and will trigger their installation, but contains no files and will not write anything to the filesystem. As such, it does not require a dist or source key to be installable.
    • composer-plugin: A package of type composer-plugin may provide an installer for other packages that have a custom type. Read more in the dedicated article.
    • symfony-bundle: Used for Symfony framework bundles to let Flex automatically enable the bundle when it’s installed.
  3. Dependencies requireand require-dev: We tell Composer which packages our project depends on such as php for all envs and phpunit for dev env.
  4. PSR-4 autoload mapping : The namespace of your package that will be mapped to your root package (src) . By default inspired by the package name.

Project package tree after confirming the generation:

sfmok ~/my-package> tree
.
├── composer.json
├── src
│   └── HelloWord.php
└── tests
    └── HelloWordTest.php

As you can see the new composer.json file has been added to our project package.

Unfortunately, thecomposer init command does not support all configuration parameters such as:

  • PSR-4 autoload-dev: used to specify a different namespace of the test/dev paths.
  • Config
  • Scripts

You can add them manually by updating composer.json file. In our example package, I’ve added PSR-4 autoload-dev namespace.

{
    "name": "sfmok/hello-world",
    "description": "A hello world example package.",
    "type": "library",
    "require": {
        "php": "^8.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.0"
    },
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "Sfmok\\HelloWorld\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Sfmok\\HelloWorld\\Tests\\": "tests/"
        }
    },
    "authors": [
        {
            "name": "Mokhtar Tlili",
            "email": "tlili.mokhtar@gmail.com"
        }
    ]
}

Just before jumping to the next step, there are a few things we must take care of them:

  • Make sure your tests are green.
  • Add a README file where you describe the package plus some details about how to use it.
  • Add .gitignore file to skip tracking unwanted files and directories such as vendor folder for dependencies packages, IDE folder (often hidden) and so on …
  • Add .gitattributes file (Optionally), it uses for many useful cases like ignoring exporting specific files or directories when composer archiving the package, it’s content are relative paths followed by an attribute e.g.: /tests export-ignore

Publish Package:

To publish the package officially, do these two steps:

  1. Host the package in a public repository (on Github for example).
  2. Register the package on Packagist.
Host the package on Github:

Create a new repository on Github and use the package name as the name of the repository (in our case “hello-world”).

Github — Repository

Once the repository is created, we need to versioning our package by initializing a local repository and then track and commit the whole project.

sfmok ~/my-package> git init --initial-branch=main
Initialized empty Git repository in /Users/sfmok/projects/my-package/.git/
sfmok ~/my-package (main)> git add .
sfmok ~/my-package (main)> git commit -m "first commit"
[main (root-commit) 25b914a] first commit
 6 files changed, 68 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 README.md
 create mode 100644 composer.json
 create mode 100644 src/HelloWorld.php
 create mode 100644 tests/HelloWorldTest.php

Afterwards, we will publish the package to Github by pushing the main branch to our public Github repository. 

sfmok ~/my-package (main)> git remote add origin https://github.com/sfmok/hello-world.git
sfmok ~/my-package (main)> git push -u origin main
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (10/10), 1.29 KiB | 661.00 KiB/s, done.
Total 10 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/sfmok/hello-world.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

Let’s take a look at our repo <https://github.com/sfmok/hello-world>

Github — Repository Package

Cool isn’t it? 

Last step on Github we need to create the first release version v1.0.0. To do that let’s go to the release section and create a new release:

Each release needs a tag so we must create a tag first.

Github — Releases Package

Then fill out the release form and publish:

Github — Releases Package

Well done. Our first release has been published on Github and is ready to be registered in Packagist.

Register the package on Packagist:

In order to register the package we need to log into Packagist (better to login via Github account).

Once you logged in we have to submit our package.

Packagist — Submit Package

Once you clicked on the check button Packagist will do some checking to verify unique package name that’s why I noted early that the vendor part in package name should be unique.

If the checking passes well, you should get a button to submit the package.

Packagist — Submit Package

After submitting, Packagist needs to crawl the package this operation might take a few minutes afterwards it will be published.

Packagist — View Package

Congratulation! we’ve finally published our package officially and it’s ready to use in any PHP project.

Let’s give it a try by requiring the package in a PHP project composer require sfmok/hello-world

HelloWorld Package — Installation

Awesome, It works and Composer detects our first release v1.0.0.

Done! Hope you enjoyed reading it.

Cheers!!