Laravel Package Development with Local Composer Dependencies
Tags: laravel, webdev, php
Published: 2022-10-06 12:19:39

## 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](https://github.com/ash-jc-allen/short-url) (`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](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks).

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](https://ashallendesign.co.uk/blog/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:

```json
"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:

```json
{
    "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`:

```text
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):

```text
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:

```text
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:

```shell
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:

```text
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:

```text
composerLocal local "./packages/*"
```

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

```text
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:

```text
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:

```text
composerLocal short-url
```

This would update our `repositories` field to look like so:

```json
"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:

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

This would update our `repositories` field to look like so:

```json
"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](https://packagist.org), 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:

```text
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:

```text
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:

```text
composerVcs short-url git@github.com:Sammyjo20/short-url.git
```

This would update our `repositories` field to look like so:

```json
"repositories": {
    "short-url": {
        "type": "vcs",
        "url": "git@github.com:Sammyjo20/short-url.git"
    }
}
```

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

```text
composer config repositories.short-url '{"type": "vcs", "url": "git@github.com: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:

```text
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:

```text
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](https://ashallendesign.co.uk/blog).

Keep on building awesome stuff! 🚀