# Publishing Your Own PHP Package on Packagist with Composer

> I configured a PHP package from scratch and published it on Packagist so others could use it — here's how I did it.

- Published: 2015-11-01
- Category: Tools & Technologies
- Tags: PHP, Composer
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/publishing-your-own-php-package-on-packagist-with-composer/
- Language: en-US
- Author: Muhammet Şafak

---
I noticed I had been copying and pasting the same helper class across several projects. Every time, I had to check whether the copy was up to date or outdated. One day I thought, "What if I turned this into a Composer package?" — so I tried it. The result was easier than I expected.

In this post I'll walk through configuring a PHP package from scratch and publishing it on **Packagist** (the PHP package registry) step by step. Packagist is the default public repository that [Composer](/en/blog/switching-to-composer-stop-managing-php-dependencies-by-hand/) looks up.

## Package structure

A Composer package is essentially a directory with a specific layout. The basic structure should look like this:

```
my-package/
├── src/
│   └── HelperClass.php
├── tests/
│   └── HelperClassTest.php
├── composer.json
├── .gitignore
└── README.md
```

PHP files live under `src/`, tests under `tests/`, and `composer.json` is the central piece that ties everything together.

## Preparing composer.json

```json
{
    "name": "muhammetsafak/yardimci",
    "description": "Günlük PHP geliştirmede işime yarayan yardımcı sınıflar",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Muhammet Şafak",
            "email": "ornek@ornek.com"
        }
    ],
    "require": {
        "php": ">=5.5"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0"
    },
    "autoload": {
        "psr-4": {
            "Muhammetsafak\\Yardimci\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Muhammetsafak\\Yardimci\\Tests\\": "tests/"
        }
    }
}
```

A few important points:

- **`name`:** Must follow the `vendor/package` format and be unique on Packagist.
- **`autoload`:** Uses PSR-4 to map a namespace to a directory. Classes under the `Muhammetsafak\Yardimci\` namespace are looked up in `src/`.
- **`require-dev`:** Dependencies needed only during development go here — they are not downloaded when a consumer runs `composer install`.

Getting `type: "library"` right matters. Packagist uses this field to decide how to classify the package. If you're building a [Laravel](/en/blog/migrating-to-laravel-5-directory-structure-and-env/) package, `type: "library"` is still valid; consumers add Laravel-specific service providers by hand to the `providers` array in their own `config/app.php`.

## Writing a simple class

```php
<?php

namespace Muhammetsafak\Yardimci;

class Metin
{
    public static function kisalt($metin, $uzunluk = 100, $son = '...')
    {
        if (mb_strlen($metin) <= $uzunluk) {
            return $metin;
        }

        return mb_substr($metin, 0, $uzunluk) . $son;
    }

    public static function slug($metin)
    {
        $metin = mb_strtolower($metin, 'UTF-8');
        $metin = str_replace(
            ['ç', 'ş', 'ğ', 'ı', 'İ', 'ö', 'ü'],
            ['c', 's', 'g', 'i', 'i', 'o', 'u'],
            $metin
        );
        $metin = preg_replace('/[^a-z0-9\s-]/', '', $metin);
        $metin = preg_replace('/[\s-]+/', '-', $metin);

        return trim($metin, '-');
    }
}
```

One thing I noticed before writing this class: all functions used in a library should be prefixed with `mb_`. Use `mb_strlen` instead of `strlen`, `mb_strtolower` instead of `strtolower`. If someone else uses your library and their text data is in an encoding other than UTF-8, plain string functions will silently produce incorrect results.

## Pushing to GitHub

Packagist works with GitHub repositories, so the package needs to be pushed to a GitHub repo.

Version tags matter. Composer looks at tags to decide which version to download. Following semantic versioning:

```bash
git tag v1.0.0
git push origin v1.0.0
```

Tags like `v1.0.0`, `v1.0.1`, and `v1.1.0` are recognized by Composer as versions.

You can also use a package via a branch name like `dev-main` without tags, but that is not a stable dependency. The consumer's `composer.json` would need `"minimum-stability": "dev"`, which is not recommended in practice. Using properly tagged versions ensures that consumers know exactly what code they are getting.

## Registering on Packagist

Log in to packagist.org with your GitHub account. On the "Submit" page, paste your GitHub repository URL — Packagist reads `composer.json` and creates the package.

After that, anyone can add the package to their project with:

```bash
composer require muhammetsafak/yardimci
```

## Webhook for automatic updates

You need to set up a webhook so Packagist is notified every time you push an update. Go to the "Webhooks" section in your GitHub repository settings; Packagist provides a URL and a token. Add both as a webhook. From that point on, every `git push` will trigger an automatic update on Packagist.

Without a webhook, you can manually trigger a "Force Update" from the Packagist dashboard — but that is a one-off solution. A webhook ensures that Packagist reflects the new information within minutes of you tagging a new release.

## .gitignore and distribution files

Beyond adding a `.gitignore`, it is good practice to create a `.gitattributes` file. In this file you can exclude the test directory and development tooling from distribution archives:

```
/tests export-ignore
/.github export-ignore
phpunit.xml export-ignore
```

This way, when a consumer runs `composer install --prefer-dist`, only the contents of `src/` are downloaded — tests and CI configuration are left out. The project's `vendor/` directory doesn't bloat unnecessarily.

## First publish experience

When I pushed the package to Packagist and installed it in another project via `composer require`, there was a strange feeling: using code I wrote myself as a dependency. For any future update, a single `composer update` across all projects is all it takes.

I fixed a bug, tagged `v1.0.1`, pushed it to GitHub. The webhook triggered an immediate update on Packagist. In the dependent projects, `composer update muhammetsafak/yardimci` pulled the latest version. Going through that cycle for the first time made me realize, once again, just how fragile copy-paste really is.

Writing a library sounds like a big undertaking, but small, single-purpose packages are genuinely useful. Versioned, shareable code instead of copy-paste. Over time, this habit also nudges you toward thinking about code in a more modular way.
