Part 1 of 12 in the PHP Package Development series

Every PHP developer uses packages daily. You composer require something, it works, and you move on. But have you ever thought about building your own? Whether it's extracting a utility you keep copying between projects or sharing a solution with the community, creating packages is one of teh best things you can do for your career and the ecosystem.

This is the first post in a 12-part series where we'll go from zero to publishing professional PHP packages - including Laravel and Symfony integration. By the end, you'll have the skills and confidence to build, test, and publish packages that other developers actually want to use.

What Is a PHP Package?

A PHP package is a reusable piece of code distributed through Composer - PHP's dependency manager. It has its own namespace, its own tests, and its own versioning. When someone runs composer require your-vendor/your-package, Composer downloads it, sets up autoloading, and it's ready to use.

Every package lives on Packagist (packagist.org) - the main Composer repository. As of 2025, Packagist hosts over 400,000 packages and serves billions of installs per month. That's the ecosystem you can tap into.

A package is not a framework or a full application. It's a focused, self-contained library that does one thing well. Think of it as a building block - monolog/monolog handles logging, guzzlehttp/guzzle handles HTTP requests, nesbot/carbon handles dates.

Why Should You Build Packages?

Stop Copying Code Between Projects

We've all done it. You build a helper class in one project, then copy it into the next one. And the next one. Eventually you have 5 slightly different versions of the same code, and a bug fix in one project never reaches the others.

A package solves this. Write it once, version it, and composer update pulls fixes everywhere.

// Before: copy-pasting this into every project
class StringHelper
{
 public static function slugify(string $text): string
 {
 $text = preg_replace('/[^\w\s-]/', '', strtolower(trim($text)));
 return preg_replace('/[\s-]+/', '-', $text);
 }
}

// After: one composer command
// composer require jakovic/text-toolkit

use Jakovic\TextToolkit\Str;

$slug = Str::slugify('Hello World!'); // "hello-world"

Build Your Reputation

Open-source packages are the best portfolio piece. They show you can write clean, tested, documented code that other developers can understand. Hiring managers notice this. A well-maintained package with 500 stars speaks louder than a resume bullet point saying "5 years PHP experience".

Write Better Code

When you know other people will read your code, you naturally write it better. You think about clean public APIs, edge cases, documentation, and backward compatibility. This discipline carries over into all your code, even code that never becomes a package.

Give Back to the Community

The PHP ecosystem runs on open source. Every composer require you run benefits from someone else's work. Building a package is how you contribute back. It doesn't have to be revolutionary - even a small utility that saves developers 30 minutes is valuable.

What Makes a Good Package?

Not every piece of code should be a package. Here's what separates good packages from the rest.

Do package this:

  • Focused utilities - text manipulation, data validation, file handling
  • API wrappers - clean interfaces for third-party services (Stripe, Twilio, etc.)
  • Framework integrations - Laravel/Symfony wrappers that add features
  • Common patterns - pagination, caching strategies, rate limiters
  • Domain-specific tools - invoice generators, PDF builders, image processors

Don't package this:

  • Project-specific business logic - your app's order processing rules
  • Thin wrappers with no value - don't wrap something that's already simple
  • Everything-and-the-kitchen-sink - a "utils" package with 200 unrelated functions
  • Something that already exists and works well - unless you have a genuinely better approach

If you've solved the same problem in 3 or more projects, it's probably worth extracting into a package. If you find yourself explaining the solution to other developers, its definitely worth sharing.

The Anatomy of a PHP Package

Before we start building, let's understand what goes into a package. Every well-structured PHP package follows a standard layout:

your-package/
 src/ # Your PHP source code
 TextToolkit.php
 Slugifier.php
 tests/ # PHPUnit or Pest tests
 SlugifierTest.php
 composer.json # Package metadata and autoloading
 README.md # Documentation
 LICENSE # Open-source license
 .gitignore # Files to exclude from Git

The most important file is composer.json. It tells Composer everything about your package - its name, dependencies, autoloading rules, and more. Here's a minimal example:

{
 "name": "jakovic/text-toolkit",
 "description": "A collection of text manipulation utilities for PHP",
 "type": "library",
 "license": "MIT",
 "require": {
 "php": "^8.1"
 },
 "autoload": {
 "psr-4": {
 "Jakovic\\TextToolkit\\": "src/"
 }
 }
}

Let's break down the key parts:

  • name - Vendor/package format. Your vendor name (or GitHub username) + the package name
  • description - A short, clear description. This shows up on Packagist search results
  • type - Almost always "library" for packages
  • license - MIT is the most common for open-source PHP packages
  • require - Dependencies, including the minimum PHP version
  • autoload - PSR-4 mapping that tells Composer where to find your classes

The autoload section maps your namespace to a directory. "Jakovic\\TextToolkit\\": "src/" means the class Jakovic\TextToolkit\Slugifier lives at src/Slugifier.php. Composer generates the autoloader automatically - no manual require statements needed.

Understanding Composer and Packagist

If you've used npm for JavaScript or pip for Python, Composer works the same way for PHP. Here's the flow:

  1. You develop your package locally and push to GitHub
  2. You register your package on Packagist (one-time setup)
  3. Packagist reads your composer.json and lists your package
  4. Developers install with composer require jakovic/text-toolkit
  5. Composer downloads the code, resolves dependencies, and sets up autoloading

Semantic Versioning

Packages use semantic versioning (SemVer) via Git tags. The format is MAJOR.MINOR.PATCH:

v1.0.0 First stable release
v1.1.0 Added new features (backward-compatible)
v1.1.1 Bug fixes only
v2.0.0 Breaking changes (renamed methods, changed signatures)

This matters because Composer uses version constraints. When someone requires "jakovic/text-toolkit": "^1.0", Composer will install any 1.x version but not 2.0 (which might break their code).

Don't jump to v1.0.0 on your first commit. Start with v0.1.0 during development. The 0.x range signals that the API might change. Only tag v1.0.0 when your public API is stable and tested.

Real-World Package Examples

Let's look at some popular packages and what makes them successful.

nesbot/carbon (200M+ installs) - A date/time library extending PHP's DateTime. It works because it solves a real pain point with a fluent, readable API: Carbon::now()->addDays(5)->isWeekend(). Comprehensive but focused - dates only, does them well.

spatie/laravel-permission (100M+ installs) - Role and permission management for Laravel. Simple to start, flexible to extend, follows Laravel conventions so it feels native. Spatie maintains it actively with clear upgrade guides.

monolog/monolog (300M+ installs) - Logging library implementing PSR-3. Handler-based architecture lets you send logs anywhere. Zero framework lock-in - used by Laravel, Symfony, and standalone apps.

Notice the pattern: each package does one thing, does it well, and has clear documentation. That's the formula.

What We'll Build in This Series

Throughout this series, we'll build a real package from scratch: jakovic/text-toolkit. It will include:

  • Slugifier - Convert text to URL-friendly slugs with Unicode support
  • Truncator - Smart text truncation that respects word boundaries
  • Excerpt - Extract summaries from long text/HTML content
  • Markdown to Text - Strip Markdown syntax to plain text

We'll cover the full lifecycle:

  1. Project setup with proper structure and autoloading
  2. Writing clean, tested code
  3. Publishing to Packagist with semantic versioning
  4. Adding quality tools (PHPStan, Pint, GitHub Actions)
  5. Building a Laravel wrapper with Blade directives and facades
  6. Building a Symfony bundle with Twig filters and DI configuration

Prerequisites

You should be comfortable with PHP 8.1+ syntax (typed properties, enums, named arguments), basic Git, and have Composer installed. If you've built a few projects with Laravel or Symfony, you're in great shape. No package development experience required - that's what we're here to learn.

Setting Up Your Environment

Before we start building in the next post, make sure you have these installed:

# Check PHP version (8.1+ required)
php -v

# Check Composer is installed
composer --version

# Check Git is installed
git --version

You'll also want a GitHub account for hosting your code, a Packagist account (sign up at packagist.org and link it to GitHub), and an IDE with PHP support (PhpStorm or VS Code with PHP extensions).

If you can run the three commands above without errors, you're ready for Part 2.

What's Next

In Part 2: Setting Up Your First Package, we'll create the jakovic/text-toolkit package from scratch. You'll learn the exact directory structure, how to configure composer.json properly, set up PSR-4 autoloading, and write your first class. We'll have a working, installable package by the end of that post.

If you have questions or topics you'd like covered in this series, drop a comment below. See you in Part 2!