Support the ongoing development of Laravel.io →
Article Hero Image

Laravel Package Development with Local Composer Dependencies

6 Oct, 2022 10 min read

Photo by Lorenzo Spoleti on Unsplash

Introduction

If you've read any of my previous blog posts, you'll know that I like contributing to open-source projects whenever I get a chance to.

To be able to work on any packages, I typically have a "playground" Laravel project that I can use to test the changes that I make. This project is more or less a fresh Laravel installation with a few routes and controllers set up for me to test my contributions.

So this means that I need to be able to work on packages inside my project without directly updating any vendor files (in my project's vendor directory). To do this, I set a few options in the project's composer.json file so that I can work on packages locally.

This is a great way for you to experiment with possible features or changes for packages that you can then propose as pull requests.

In this short guide, I'm going to show you how you can do this yourself in your own projects if you're considering working on a package (whether it be a new package you're building or an existing one that you'd like to contribute to).

Local Package Development

Before we get started, I'm first going to assume that you have a fresh Laravel installation. You don't necessarily need to have one, but this does definitely make things easier by reducing the chances of clashes with other packages.

For the purpose of this guide, we're going to get install a fork of my Short URL package (ashallendesign/short-url) as a local package. If you're not sure what a fork is, you can check out this page on GitHub that describes it well.

As a side note, if you're interested in reading a bit more about using the Short URL package in your Laravel projects, you can my past article "How to Create Short URLs in Laravel".

We first want to let Composer know that we may have some packages stored locally that we can use when running things like composer require and composer update. To do this, we can add the following block to our project's composer.json file:

"repositories": {
    "local": {
        "type": "path",
        "url": "./packages/*",
        "options": {
            "symlink": true
        }
    }
}

In the above block, we're letting Composer know that we may have some packages available in a packages directory in our project. Now, whenever we try to install any new dependencies, Composer will first try and install them from the packages folder if they exist there. Otherwise, they'll be installed as usual.

I've chosen to place them in this folder because I like keeping the local packages directly in the project itself. However, you are free to place them outside of the project if you'd wish. You'll just need to remember to update the url field to point to the correct directory.

After adding this field to our composer.json, the entire file may look something like so:

{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "require": {
        "php": "^8.0.2",
        "guzzlehttp/guzzle": "^7.2",
        "laravel/framework": "^9.11",
        "laravel/sanctum": "^2.14.1",
        "laravel/tinker": "^2.7"
    },
    "require-dev": {
        "fakerphp/faker": "^1.9.1",
        "laravel/sail": "^1.0.1",
        "mockery/mockery": "^1.4.4",
        "nunomaduro/collision": "^6.1",
        "phpunit/phpunit": "^9.5.10",
        "spatie/laravel-ignition": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    },
    "extra": {
        "laravel": {
            "dont-discover": []
        }
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "repositories": {
        "local": {
            "type": "path",
            "url": "./packages/*",
            "options": {
                "symlink": true
            }
        }
    }
}

Now that we've configured our composer.json file, we can now think about installing our Laravel package locally so that we can start working on it.

To do this, you'll first need to clone your package to your packages folder. So in my particular case, I would run the following command in my project's root to download ashallendesign/short-url:

git clone https://github.com/ash-jc-allen/short-url.git packages/short-url

Alternatively, I could also have run the following command using the GitHub CLI (which I would definitely recommend checking out if you're not already using it):

gh repo clone ash-jc-allen/short-url packages/short-url

Note: It's important to remember that if you're contributing to someone else's package that you'll likely need to download a fork of the package, rather than the actual package itself.

After you've run either of the above commands, you should now be able to see the package in your packages directory (or, in this case, packages/short-url to be specific).

Now that we have the package available locally, we can now install it using Composer as we normally would. In our particular case, we could run the following command:

composer require ashallendesign/short-url

That's it! The package should now be installed for you to locally start making changes to it.

You may also notice that if you look in your vendor directory (or, in this case, the vendor/ashallendesign directory) that you can see your package there. Although, this isn't actually the package itself and is just a symlink back to your package in the packages folder.

Using the CLI

You may find that you need to switch between local and remote versions of packages quite often. If this is the case, you might not want to keep manually updating your composer.json file manually and you may want an easier to automate the process.

To speed up this process, you can make use of a Zsh function to use directly in your command line.

It's worth noting that this is only a simple Zsh function that I've written to work on MacOS. I also have minimal experience in writing these types of scripts, so there may be a much cleaner and easier way of achieving the same thing. The commands we'll cover are also just starting points that could be expanded on to suit your own needs.

But, if you're using Zsh, the following information should help to automate the process by creating three new functions: composerLocal, composerVcs, and composerRemote.

To get started, open your ~/.zshrc file and add the following to it:

function composerLocal() {
    URL="${2:-./packages/${1}}"

    composer config repositories."$1" '{"type": "path", "url": "'"${URL}"'", "options": {"symlink": true}}' --file composer.json
}

function composerRemote() {
    composer config repositories."$1" --unset
}

function composerVcs() {
    composer config repositories."$1" '{"type": "vcs", "url": "'"${2}"'"}' --file composer.json
}

This is defining our three new functions (composerLocal, composerVcs, and composerRemote).

You'll likely need to reopen your terminal, or run the following command to make the commands available for using:

source ~/.zshrc

Now that we've added these commands, we can quickly add and remove local repositories in our project. Running them will handle the correct fields to the repositories field in our composer.json file like we previously added manually.

The composerLocal command allows us to specify that we want to use a local Composer dependency. It accepts 2 parameters:

  1. The key that should be used in the repositories field. This can be something like local or the name of the package (e.g - short-url).
  2. The directory that the fields are stored in. If this is passed, the path will be added to the composer.json file as-is. Otherwise, it will default to a ./packages/{KEY-HERE} path, where {KEY-HERE} is the first parameter that you passed to the command.

For example, if we wanted to specify that we wanted to detect any Composer packages in our project's packages directory (like we had previously done), we could run the following command:

composerLocal local "./packages/*"

Under the hood, this is just running the following Composer command for us:

composer config repositories.local '{"type": "path", "url": "./packages/*", "options": {"symlink": true}}' --file composer.json

Likewise, if the packages were stored outside the project (in a ~/www/packages directory), we could run the following command:

composerLocal local "~/www/package-dev/packages/*"

This command could also be used to define individual packages that should be installed, rather than a catch-all. For example, if we wanted to say that we only wanted to install ashallendesign/short-url and we had a local copy in our project's packages directory, we could run the following command:

composerLocal short-url

This would update our repositories field to look like so:

"repositories": {
    "short-url": {
        "type": "path",
        "url": "./packages/short-url",
        "options": {
            "symlink": true
        }
    }
}

Or, we wanted to install the package but it was located in a ~/www/packages/short-url directory, we could run the following command:

composerLocal short-url "~/www/package-dev/packages/short-url"

This would update our repositories field to look like so:

"repositories": {
    "short-url": {
        "type": "path",
        "url":  "~/www/package-dev/packages/short-url",
        "options": {
            "symlink": true
        }
    }
}

If we wanted to ignore these packages locally and use the remote versions provided through Packagist, we could run the following composerRemote command. This command accepts one parameter:

  1. The key that should be removed from the repositories field in the composer.json file.

For example, if we wanted to stop using the local version of ashallendesign/short-url and start using the remote version, we could run the following command:

composerRemote short-url

This command will remove the field with the short-url key from the repositories field in the composer.json file.

Under the hood, this is just running the following Composer command for us:

composer config repositories.short-url --unset

Alternatively, if you want to switch to using a package that's defined on a version control system (such as GitHub), you can use the composerVcs command. This is ideal if you are using a package that is your own fork or is in a private repository, and so isn't listed on Packagist.

The composerVcs command requires two parameters:

  1. The key that should be used in the repositories field. This would typically be the name of the package (e.g - short-url).
  2. The key to the repository. If this is a private repository, you may need to use the SSH link provided by the version control system (such as GitHub).

For example, if we imagine that we have our own private fork of ashallendesign/short-url in a repository at https://github.com/Sammyjo20/short-url, we could run the following command:

composerVcs short-url [email protected]:Sammyjo20/short-url.git

This would update our repositories field to look like so:

"repositories": {
    "short-url": {
        "type": "vcs",
        "url": "[email protected]:Sammyjo20/short-url.git"
    }
}

Under the hood, this is just running the following Composer command for us:

composer config repositories.short-url '{"type": "vcs", "url": "[email protected]:Sammyjo20/short-url.git"}' --file composer.json

It's important to remember that after running the composerLocal, composerRemote, and composerVcs functions you remember to run composer dump-autoload and then re-require your package (for example, by running the command composer require ashallendesign/short-url.

So for example, to add ashallendesign/short-url as a local dependency, we could run the following command:

composerLocal short-url && composer dump-autoload && composer require ashallendesign/short-url

Then if we wanted to use the remote version of the package, we could run:

composerRemote short-url && composer dump-autoload && composer require ashallendesign/short-url

Conclusion

Hopefully, this post should have given you a quick overview you how you can use and edit local Composer packages in your Laravel projects.

If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.

If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter.

Keep on building awesome stuff! 🚀

Last updated 1 month ago.

driesvints, shroomok liked this article

2
Like this article? Let the author know and give them a clap!
ash-jc-allen (Ash Allen) I'm a freelance Laravel web developer from Preston, UK. I maintain the Ash Allen Design blog and get to work on loads of cool and exciting projects 🚀

Other articles you might like

Article Hero Image November 18th 2024

Laravel Custom Query Builders Over Scopes

Hello 👋 Alright, let's talk about Query Scopes. They're awesome, they make queries much easier to r...

Read article
Article Hero Image November 19th 2024

Access Laravel before and after running Pest tests

How to access the Laravel ecosystem by simulating the beforeAll and afterAll methods in a Pest test....

Read article
Article Hero Image November 11th 2024

🍣 Sushi — Your Eloquent model driver for other data sources

In Laravel projects, we usually store data in databases, create tables, and run migrations. But not...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.