Laravel Package Development with Local Composer Dependencies
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:
- The key that should be used in the
repositories
field. This can be something likelocal
or the name of the package (e.g -short-url
). - 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:
- The key that should be removed from the
repositories
field in thecomposer.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:
- The key that should be used in the
repositories
field. This would typically be the name of the package (e.g -short-url
). - 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! 🚀
driesvints, shroomok liked this article