master 55c47cfdd7fa cached
27 files
117.6 KB
28.0k tokens
164 symbols
1 requests
Download .txt
Repository: Letudiant/composer-shared-package-plugin
Branch: master
Commit: 55c47cfdd7fa
Files: 27
Total size: 117.6 KB

Directory structure:
gitextract_uxe5wr2i/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── docs/
│   ├── all-available-configurations.md
│   └── how-to-use/
│       ├── disable-this-plugin-in-development-environment.md
│       ├── update-only-your-own-packages.md
│       └── work-with-satis.md
├── phpunit.xml.dist
├── src/
│   └── LEtudiant/
│       └── Composer/
│           ├── Data/
│           │   └── Package/
│           │       ├── PackageDataManagerInterface.php
│           │       └── SharedPackageDataManager.php
│           ├── Installer/
│           │   ├── Config/
│           │   │   └── SharedPackageInstallerConfig.php
│           │   ├── SharedPackageInstaller.php
│           │   └── Solver/
│           │       ├── SharedPackageInstallerSolver.php
│           │       └── SharedPackageSolver.php
│           ├── SharedPackagePlugin.php
│           └── Util/
│               └── SymlinkFilesystem.php
└── tests/
    ├── bootstrap.php
    └── unit/
        └── Test/
            └── Unit/
                └── LEtudiant/
                    └── Composer/
                        ├── Data/
                        │   └── Package/
                        │       └── SharedPackageDataManagerTest.php
                        ├── Installer/
                        │   ├── Config/
                        │   │   └── SharedPackageInstallerConfigTest.php
                        │   ├── SharedPackageInstallerTest.php
                        │   └── Solver/
                        │       ├── SharedPackageInstallerSolverNotSharedTest.php
                        │       ├── SharedPackageInstallerSolverSharedTest.php
                        │       └── SharedPackageSolverTest.php
                        ├── SharedPackagePluginTest.php
                        └── Util/
                            └── SymlinkFilesystemTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
tests/build
vendor
composer.lock
composer.phar
phpunit.xml

================================================
FILE: .travis.yml
================================================
language: php

php:
    - 5.5
    - 5.6
    - 7.0

before_script:
    - curl -s http://getcomposer.org/installer | php
    - php composer.phar install  --no-interaction --prefer-source

script:
    - ./vendor/bin/phpunit

after_script:
    - ./vendor/bin/test-reporter --coverage-report=tests/build/logs/clover.xml --stdout > codeclimate.json
    - "curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports"

addons:
    code_climate:
        repo_token:
            secure: "J5qoMlEWXejZ7F3/9oH6jnXPg7eUdB982jczrcCQWYYCCMmQTqTuKBPXRM+HtaE6UyMvtk5um/WXWwbDCmrb7NmOpgfIaPtjyhGvmg/q71stCkt8QapMEwQKRB7HSzIjsFcsc2kCPVew93x5lTEFWV+Hu+apFUOGVr/mn/1j/3g="


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 L'Etudiant

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# Composer - Shared Package Plugin

[![Code Climate](https://codeclimate.com/github/Letudiant/composer-shared-package-plugin/badges/gpa.svg)](https://codeclimate.com/github/Letudiant/composer-shared-package-plugin)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Letudiant/composer-shared-package-plugin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Letudiant/composer-shared-package-plugin/?branch=master)
[![Build Status](https://travis-ci.org/Letudiant/composer-shared-package-plugin.svg?branch=master)](https://travis-ci.org/Letudiant/composer-shared-package-plugin)
[![Test Coverage](https://codeclimate.com/github/Letudiant/composer-shared-package-plugin/badges/coverage.svg)](https://codeclimate.com/github/Letudiant/composer-shared-package-plugin)

This composer plugin allows you to share **your selected packages between your projects by creating symlinks**.  
All shared packages will be in the same dedicated directory for all of your projects (ordered by versions) and a symlink directory container will be created on your projects (`vendor-shared` by default).

**This plugin will improve your work process** to avoid to work into the `vendor` folder or to avoid to force you to push your package to work/test it with another project.

* [How it works](#how-it-works)
* [Installation](#installation)
* [Structure generation example](#structure-generation-example)
* [How to use (known issues)](#how-to-use-known-issues)
 * [Update only your own packages](./docs/how-to-use/update-only-your-own-packages.md)
 * [Disable this plugin in development environment (for CI purpose, for example)](./docs/how-to-use/disable-this-plugin-in-development-environment.md)
 * [Work with Satis : increase the Composer speed](./docs/how-to-use/work-with-satis.md)
* [All available configurations](#all-available-configurations)
* [Reporting an issue or a feature request](#reporting-an-issue-or-a-feature-request)
* [ChangeLog](#changelog)
* [Credit](#credit)
* [License](#license)

## How it works

A shared package is flagged by two ways :

* By setting the root project `composer.json` extra configuration `package-list` with the selected package name *(works only with the `>= 2.x` version)*.
* By setting the `composer.json` package `type`  to `shared-package` *(the `<= 1.x` version way, still works on `2.x`)*.



If this composer plugin is required in the root project `composer.json` : the package will be downloaded in the dedicated dependencies directory that you provided and a symlink will be created in the project `vendor-shared` directory *(by default)*.  

This plugin allows you to work with many versions at the same time for a package by creating a sub-directory named by the version of your package *(dev-master, dev-develop, 1.0.x-dev, etc)*.

A `packages.json` file is created in the dependencies sources directory to know which projects use a package version and be able to ask you if you want to delete the version directory during the Composer uninstall process, if no project seems to use it.

## Installation

### Step 1 : edit your root composer.json

Add, to your root project `composer.json`, this require **(in dev only)** :

``` json
// composer.json (project)
{
    "require-dev": {
        "letudiant/composer-shared-package-plugin": "~2.0"
    }
}
```

**Note:** this plugin works fine in production mode, but it has been created for development purpose.

### Step 2 : set your dependencies vendor path

Your dependencies vendor path is the path where all your shared packages will be downloaded. This path should be at the same level (or above) of all your projects.  
If you IDE doesn't handle symlinks, you may use this directory to work on your development packages. Otherwise, you'll be able to work directly on your symlinks with modern IDE (PHP Storm, SublimeText, ...).

Add, to your root project `composer.json`, this extra configuration :

``` json
// composer.json
{
    "extra": {
        "shared-package": {
            "vendor-dir": "/path/to/your/dependencies/directory"
        }
    }
}
```

**Note:** you can pass a relative path  (`foo/bar`) or absolute path (starts with "/" : `/foo/bar`).  
If your path is relative, your symlink directory base path will be relative too.

**Note for VM users:** you can manually override the symlink directory base path with the configuration `symlink-base-path` if your host machine dependencies directory path is not the same as your guest machine, see [all available configurations](./docs/all-available-configurations.md) page for more information.

### Step 3 : select your shared packages

Add, in your own package `composer.json`, which one you want to share between your projects :

``` json
// composer.json
{
    "extra": {
        "shared-package": {
            "vendor-dir": "/path/to/your/dependencies/directory",
            "package-list": [
                "foo/bar",
                "bar/*"
            ]
        }
    }
}
```

**Note:** as you can see, you can pass a wild card `*` to the package name. So, in this example, all packages that starts with `bar/` will be shared.
**Note²:** you can set a package name to `*` to share **all packages**.

### Step 4 : (re)install your dependencies

*If you already have installed your project dependencies, you have to fully delete your `vendor/` directory and your `composer.lock` file.*  
Run the `composer install` command.

You should see a new `vendor-shared` folder with all shared packages symlinks.

### Step 5 : play with require-dev :

You can avoid to have two project `composer.json` by setting your `require` dependencies on a stable version (`~x.x.x`) and work on dev environement with a your working in progress version by setting, in your `require-dev` with your development version, like this :

``` json
// composer.json (project)
{
    "require": {
        "acme/foo-bar": "~1.0"
    },
    "require-dev": {
        "acme/foo-bar": "dev-develop as 1.0"
    }
}
```

Thanks to that, you will be able to work with development version in dev environement and have stable version in production.

**Note:** the alias `* as 1.0` may avoid a Composer version solver error, because this behavior is not handled by default.  
**Note²:** Composer has not been created to work with development version/branch, so when you run a `composer install`, the current package branch `HEAD` commit will flagged in your `composer.lock`.  
So, the next time you'll run this command on dev environement, and if you already have a `composer.lock` file, Composer will checkout the flagged commit and not the new `HEAD` *(if you made new commit)* of your branch : **your shared packages won't be up to date**. To avoid this behavior, please read "[How to use - Update only your own packages](./docs/how-to-use/update-only-your-own-packages.md)".

## Structure generation example

Here, a complete example. Our own shared package is called `acme/foo-bar`.

``` json
// composer.json (project)
{
    "require": {
        "letudiant/composer-shared-package-plugin": "~1.0",
        "symfony/console": "~2.6",
        "acme/foo-bar": "~1.0"
    },
    "require-dev": {
        "acme/foo-bar": "dev-develop as 1.0"
    },
    "extra": {
        "shared-package": {
            "vendor-dir": "../composer-dependencies",
            "package-list": [
                "acme/foo-bar"
            ]
        }
    }
}
```

With this `composer.json`, the structure will look like :

``` bash
|-- packages.json
|-- composer-dependencies/
|   +-- acme/
|       +-- foo-bar/
|           +-- dev-develop/
|               |-- src/
|               |-- composer.json
|           +-- ...
+-- project/
+-- src/
+-- vendor/
|   +-- symfony/
|       +-- console/
|           +-- ...
|-- vendor-shared/
|   +-- acme/
|       +-- foo-bar/ (symlink to "../../../composer-dependencies/acme/foo-bar/dev-develop/")
+-- ...
```

## How to use (and known issues)

This plugin implement a new behavior which is not handled by Composer, so there are a few known issues. Here, the way to fix them :

* [Update only your own packages](./docs/how-to-use/update-only-your-own-packages.md)
* [Disable this plugin in development environment (for CI purpose, for example)](./docs/how-to-use/disable-this-plugin-in-development-environment.md)
* [Work with Satis : increase the Composer speed](./docs/how-to-use/work-with-satis.md)


## All available configurations

See the [all available configurations documentation](./docs/all-available-configurations.md).

## Reporting an issue or a feature request

Feel free to open an issue, fork this project or suggest an awesome new feature in [the issue tracker](https://github.com/Letudiant/composer-shared-package-plugin/issues).

## ChangeLog

### 3.1.0

* Implement environment variables, as suggested by [babwar](https://github.com/babwar), to allow to override the default configuration in `composer.json` file - [More information](./docs/all-available-configurations.md).

### 3.0.0 :

* Fix the BC update in Composer with `getInstallPath` method.

### 2.0.0 :

* Implement the possibility to choice each package you want to share with the configuration `package-list` - [More information](./docs/all-available-configurations.md).
* Delete conditions on stable/dev version. Now a shared package is shared on stable version too (tag).

### 1.2.0 :

* Rewrite installer, the installer choice process is now in a dedicated class.
* Implement new `symlink-enabled` configuration to allow to enable/disable the symlink creation process - [More information](./docs/all-available-configurations.md).

### 1.1.0 :

* Implement new `symlink-base-path` configuration, as suggested by [philbates35](https://github.com/philbates35), to allow VM users to override the symlink directory base path, see [issue](https://github.com/Letudiant/composer-shared-package-plugin/issues/1) - [More information](./docs/all-available-configurations.md).

## Credit

![L'Étudiant](http://www.letudiant.fr/etucmsEtuPlugin/images/header/logo.png)

This plugin project is maintained by **[L'Etudiant](https://github.com/Letudiant)**.  
The Composer project is maintained by Nils Adermann & Jordi Boggiano, see https://github.com/composer/composer#authors for more information.

## License

This plugin is licensed under MIT license, see the [LICENSE file](./LICENSE) for more information.  
You can also read the [Composer license](https://github.com/composer/composer/blob/master/LICENSE) for more information.


================================================
FILE: composer.json
================================================
{
    "name": "letudiant/composer-shared-package-plugin",
    "description": "This composer plugin allows you to share selected packages between your projects with symlinks.",
    "type": "composer-plugin",
    "license": "MIT",
    "authors": [
        {
            "name": "Sylvain Lorinet",
            "email": "sylvain.lorinet@gmail.com"
        }
    ],
    "support": {
        "issues": "https://github.com/letudiant/composer-shared-package-plugin/issues"
    },
    "require": {
        "php": ">=5.3.2",
        "composer-plugin-api": "~1.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.7",
        "composer/composer": "dev-master",
        "codeclimate/php-test-reporter": "dev-master",
        "symfony/var-dumper": "~2.7"
    },
    "autoload": {
        "psr-0": {
            "LEtudiant\\Composer\\": "src/"
        }
    },
    "autoload-dev": {
        "Test\\Unit\\LEtudiant\\Composer\\": "tests/unit"
    },
    "extra": {
        "class": "LEtudiant\\Composer\\SharedPackagePlugin",
        "branch-alias": {
            "dev-master": "3.1-dev"
        }
    }
}


================================================
FILE: docs/all-available-configurations.md
================================================
# Composer - Shared Package Plugin

## All available configurations

All these configuration should set in the `extra` `shared-package`configuration key in your project `composer.json`.

* `vendor-dir` *(required)* : your shared packages sources directory. This folder is used by the project Composer autoloader. Relative or absolute path are allowed.  
**Note:** you can use the environment variable `COMPOSER_SPP_VENDOR_DIR` to override the default value.
* `package-list` : the shared packages list. You can pass the entire package name, or add the wildcard `*` in the name (e.g. `bar/*`). If you want to share all your project packages, set a package name to `*`.
* `symlink-dir` : your symlinks container directory on your project *(default: vendor-shared)*. Relative or absolute path are allowed.
* `symlink-base-path` : the source base path for all of your symlinks. By default, it's the `vendor-dir` path, but you can override this configuration. It's useful if you use a Virtual Machine and if your dependency directory path is not the same on both machines. Relative or absolute path are allowed. If you choose to set a relative path, it should start from your project root directory (where your project `composer.json` file is located).  
**Note:** you can use the environment variable `COMPOSER_SPP_SYMLINK_BASE_PATH` to override the default value.
* `symlink-enabled` : *(boolean, default: true)* enable or not the symlink directory creation process. Useful if you work directly with the sources directory and you don't want to have the symlink directory in your project.

### Example

``` json
// composer.json (project)
{
    "extra": {
        "shared-package": {
            "vendor-dir": "/var/projects/composer-dependencies",
            "symlink-dir": "symlinks-folder",
            "symlink-base-path": "/home/www/projects/composer-dependencies",
            "symlink-enabled": true,
            "package-list": [
                "foo/bar",
                "bar/*",
                "*"
            ]
        }
    }
}
```


================================================
FILE: docs/how-to-use/disable-this-plugin-in-development-environment.md
================================================
# Composer - Shared Package Plugin

## How to use

### Disable this plugin in development environment (for CI purpose, for example)

Just add the `--no-plugins` flag to your `install/update` command.  
Composer will disable all plugins, including this one.

`composer install --no-plugins`

If you run this command on a Continuous Integration, don't forget to [update your own packages](./update-only-your-own-packages.md) after.

### Next

See [Work with Satis : increase the Composer speed](./work-with-satis.md).


================================================
FILE: docs/how-to-use/update-only-your-own-packages.md
================================================
# Composer - Shared Package Plugin

## How to use

### Update only your own packages

You can easily update your own package, without updating stable packages. Indeed, Composer allows the wildcard `*` with the `update` command. Imagine that your packages are prefixed by `acme` (`acme/cache`, `acme/awesome-component`, ...), you should execute this command :

`composer update "acme*"`

If you have packages without the same prefix, no worry, just execute this command :

`composer update "acme*" "other-prefix*" "yet-another-prefix*"`

So, when you run a `composer install`, don't forget to run the `composer update "prefix*"` command  after to update your own shared packages because the `composer.lock` keeps only the installation commit reference *(after the first `composer install` command)*, and not your branches `HEAD`.

### Next

See [Disable this plugin in development environment (for CI purpose, for example)](./disable-this-plugin-in-development-environment.md).


================================================
FILE: docs/how-to-use/work-with-satis.md
================================================
# Composer - Shared Package Plugin

## How to use

### Work with Satis : increase Composer speed

As we know, Composer isn't the fastest tool in the world. But it does its job well.  
Unfortunately, **with custom repositories**, Composer is very slow because it reads each repository `composer.json` file (run a `composer update -v`, you'll see).  
The only way to increase the Composer speed, is to install a proxy to host our custom repositories : Satis can do this, and it's open-source.

For more information about the Satis installation/configuration, please read [the official documentation](https://getcomposer.org/doc/articles/handling-private-packages-with-satis.md#satis).

With 10 customs repositories, Composer took about 2/3 minutes to proceed the `update` command and only ~10 seconds after than Satis was installed.

### Next

See [Update only your own packages](./update-only-your-own-packages.md).


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
         bootstrap="tests/bootstrap.php"
        >
    <testsuites>
        <testsuite name="Composer Shared Package Plugin Test Suite">
            <directory suffix="Test.php">tests/unit</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>src</directory>
        </whitelist>
    </filter>

    <logging>
        <log type="coverage-html" target="tests/build/coverage" title="Rocket ORM"
             charset="UTF-8" yui="true" highlight="true"
             lowUpperBound="35" highLowerBound="70"/>
        <log type="coverage-clover" target="tests/build/logs/clover.xml"/>
        <log type="junit" target="tests/build/logs/junit.xml" logIncompleteSkipped="false"/>
    </logging>
</phpunit>


================================================
FILE: src/LEtudiant/Composer/Data/Package/PackageDataManagerInterface.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Data\Package;

use Composer\Package\PackageInterface;


/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
interface PackageDataManagerInterface
{
    /**
     * Add a row in the "packages.json" file, with the project name for the "package/version" key
     *
     * @param PackageInterface $package
     */
    public function addPackageUsage(PackageInterface $package);

    /**
     * Remove the row in the "packages.json" file
     *
     * @param PackageInterface $package
     */
    public function removePackageUsage(PackageInterface $package);

    /**
     * Return usage of the current package
     *
     * @param PackageInterface $package
     *
     * @return array
     */
    public function getPackageUsage(PackageInterface $package);

    /**
     * @param PackageInterface $package
     */
    public function setPackageInstallationSource(PackageInterface $package);

    /**
     * Set the vendor directory to save the "packages.json" file
     *
     * @param string $vendorDir
     */
    public function setVendorDir($vendorDir);
}


================================================
FILE: src/LEtudiant/Composer/Data/Package/SharedPackageDataManager.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Data\Package;

use Composer\Composer;
use Composer\Package\PackageInterface;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SharedPackageDataManager implements PackageDataManagerInterface
{
    const PACKAGE_DATA_FILENAME       = 'packages.json';
    const PACKAGE_INSTALLATION_SOURCE = 'source';

    /**
     * @var Composer
     */
    protected $composer;
    /**
     * @var string
     */
    protected $vendorDir;

    /**
     * @var array
     */
    protected $packagesData;


    /**
     * @param Composer $composer
     */
    public function __construct(Composer $composer)
    {
        $this->composer = $composer;
    }

    /**
     * @param string $vendorDir
     */
    public function setVendorDir($vendorDir)
    {
        $this->vendorDir = $vendorDir;
    }

    /**
     * @param PackageInterface $package
     * @param array            $packageData
     */
    protected function updatePackageUsageFile(PackageInterface $package, array $packageData)
    {
        $packageKey = $package->getPrettyName() . '/' . $package->getPrettyVersion();

        // Remove the row if there is no data anymore
        if (!isset($packageData[0])) {
            if (isset($this->packagesData[$packageKey])) {
                unset($this->packagesData[$packageKey]);
            }
        } elseif (!isset($this->packagesData[$packageKey])) {
            $this->packagesData[$packageKey] = array(
                // Force "source" installation to ensure that we download VCS files
                'project-usage' => $packageData
            );
        } else {
            $this->packagesData[$packageKey]['project-usage'] = $packageData;
        }

        file_put_contents(
            $this->vendorDir . DIRECTORY_SEPARATOR . self::PACKAGE_DATA_FILENAME,
            json_encode($this->packagesData)
        );
    }

    /**
     * Add a row in the "packages.json" file, with the project name for the "package/version" key
     *
     * @param PackageInterface $package
     */
    public function addPackageUsage(PackageInterface $package)
    {
        $usageData = $this->getPackageUsage($package);
        $packageName = $this->composer->getPackage()->getName();

        if (!in_array($packageName, $usageData)) {
            $usageData[] = $packageName;
        }

        $this->updatePackageUsageFile($package, $usageData);
    }

    /**
     * Remove the row in the "packages.json" file
     *
     * @param PackageInterface $package
     */
    public function removePackageUsage(PackageInterface $package)
    {
        $usageData = $this->getPackageUsage($package);
        $newUsageData = array();
        $projectName = $this->composer->getPackage()->getName();

        foreach ($usageData as $usage) {
            if ($projectName !== $usage) {
                $newUsageData[] = $usage;
            }
        }

        $this->updatePackageUsageFile($package, $newUsageData);
    }

    /**
     * Return usage of the current package
     *
     * @param PackageInterface $package
     *
     * @return array
     */
    public function getPackageUsage(PackageInterface $package)
    {
        return $this->getPackageDataKey($package, 'project-usage', array());
    }

    /**
     * Initialize the package data array if not set
     */
    protected function initializePackageData()
    {
        $filePath = $this->vendorDir . DIRECTORY_SEPARATOR . self::PACKAGE_DATA_FILENAME;
        if (!is_file($filePath)) {
            $this->packagesData = array();
        } else {
            $this->packagesData = json_decode(file_get_contents($filePath), true);
        }
    }

    /**
     * @param PackageInterface $package
     * @param string           $key
     * @param mixed            $defaultValue
     *
     * @return mixed
     */
    protected function getPackageDataKey(PackageInterface $package, $key, $defaultValue = null)
    {
        if (!isset($this->packagesData)) {
            $this->initializePackageData();
        }

        $packageKey = $package->getPrettyName() . '/' . $package->getPrettyVersion();
        if (!isset($this->packagesData[$packageKey]) || !isset($this->packagesData[$packageKey][$key])) {
            return $defaultValue;
        }

        return $this->packagesData[$packageKey][$key];
    }

    /**
     * @param PackageInterface $package
     */
    public function setPackageInstallationSource(PackageInterface $package)
    {
        $package->setInstallationSource(static::PACKAGE_INSTALLATION_SOURCE);
    }
}


================================================
FILE: src/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfig.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Installer\Config;

use LEtudiant\Composer\Installer\SharedPackageInstaller;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @see https://github.com/Letudiant/composer-shared-package-plugin/blob/master/docs/all-available-configurations.md
 */
class SharedPackageInstallerConfig
{
    const ENV_PARAMETER_VENDOR_DIR        = 'COMPOSER_SPP_VENDOR_DIR';
    const ENV_PARAMETER_SYMLINK_BASE_PATH = 'COMPOSER_SPP_SYMLINK_BASE_PATH';


    /**
     * @var string
     */
    protected $originalVendorDir;

    /**
     * @var string
     */
    protected $symlinkDir;

    /**
     * @var string
     */
    protected $vendorDir;

    /**
     * @var string|null
     */
    protected $symlinkBasePath;

    /**
     * @var bool
     */
    protected $isSymlinkEnabled = true;

    /**
     * @var array
     */
    protected $packageList = array();


    /**
     * @param string     $originalRelativeVendorDir
     * @param string     $originalAbsoluteVendorDir
     * @param array|null $extraConfigs
     */
    public function __construct($originalRelativeVendorDir, $originalAbsoluteVendorDir, $extraConfigs)
    {
        if (!isset($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['vendor-dir'])) {
            throw new \InvalidArgumentException(
                'The "vendor-dir" parameter for "' . SharedPackageInstaller::PACKAGE_TYPE . '" configuration '
                . 'should be provided in your project composer.json ("extra" key)'
            );
        }

        $this->originalVendorDir = $originalRelativeVendorDir;

        $baseDir = substr($originalAbsoluteVendorDir, 0, -strlen($this->originalVendorDir));

        $this->setVendorDir($baseDir, $extraConfigs);
        $this->setSymlinkDirectory($baseDir, $extraConfigs);
        $this->setSymlinkBasePath($extraConfigs);
        $this->setIsSymlinkEnabled($extraConfigs);
        $this->setPackageList($extraConfigs);
    }

    /**
     * @param string $baseDir
     * @param array  $extraConfigs
     */
    protected function setSymlinkDirectory($baseDir, array $extraConfigs)
    {
        $this->symlinkDir = $baseDir . 'vendor-shared';

        if (isset($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-dir'])) {
            $this->symlinkDir = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-dir'];

            if ('/' != $this->symlinkDir[0]) {
                $this->symlinkDir = $baseDir . $this->symlinkDir;
            }
        }
    }

    /**
     * @param string $baseDir
     * @param array  $extraConfigs
     *
     * @throws \InvalidArgumentException
     */
    protected function setVendorDir($baseDir, array $extraConfigs)
    {
        $this->vendorDir = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['vendor-dir'];

        if (false !== getenv(static::ENV_PARAMETER_VENDOR_DIR)) {
            $this->vendorDir = getenv(static::ENV_PARAMETER_VENDOR_DIR);
        }

        if ('/' != $this->vendorDir[0]) {
            $this->vendorDir = $baseDir . $this->vendorDir;
        }
    }

    /**
     * Allow to override symlinks base path.
     * This is useful for a Virtual Machine environment, where directories can be different
     * on the host machine and the guest machine.
     *
     * @param array $extraConfigs
     */
    protected function setSymlinkBasePath(array $extraConfigs)
    {
        if (isset($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-base-path'])) {
            $this->symlinkBasePath = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-base-path'];

            if (false !== getenv(static::ENV_PARAMETER_SYMLINK_BASE_PATH)) {
                $this->symlinkBasePath = getenv(static::ENV_PARAMETER_SYMLINK_BASE_PATH);
            }

            // Remove the ending slash if exists
            if ('/' === $this->symlinkBasePath[strlen($this->symlinkBasePath) - 1]) {
                $this->symlinkBasePath = substr($this->symlinkBasePath, 0, -1);
            }
        } elseif (0 < strpos($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['vendor-dir'], '/')) {
            $this->symlinkBasePath = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['vendor-dir'];
        }

        // Up to the project root directory
        if (0 < strpos($this->symlinkBasePath, '/')) {
            $this->symlinkBasePath = '../../' . $this->symlinkBasePath;
        }
    }

    /**
     * The symlink directory creation process can be disabled.
     * This may mean that you work directly with the sources directory so the symlink directory is useless.
     *
     * @param array $extraConfigs
     */
    protected function setIsSymlinkEnabled(array $extraConfigs)
    {
        if (isset($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-enabled'])) {
            if (!is_bool($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-enabled'])) {
                throw new \UnexpectedValueException('The configuration "symlink-enabled" should be a boolean');
            }

            $this->isSymlinkEnabled = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['symlink-enabled'];
        }
    }

    /**
     * @return array
     */
    public function getPackageList()
    {
        return $this->packageList;
    }

    /**
     * @param array $extraConfigs
     */
    public function setPackageList(array $extraConfigs)
    {
        if (isset($extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['package-list'])) {
            $packageList = $extraConfigs[SharedPackageInstaller::PACKAGE_TYPE]['package-list'];

            if (!is_array($packageList)) {
                throw new \UnexpectedValueException('The configuration "package-list" should be a JSON object');
            }

            $this->packageList = $packageList;
        }
    }

    /**
     * @return bool
     */
    public function isSymlinkEnabled()
    {
        return $this->isSymlinkEnabled;
    }

    /**
     * @return string
     */
    public function getVendorDir()
    {
        return $this->vendorDir;
    }

    /**
     * @return string
     */
    public function getSymlinkDir()
    {
        return $this->symlinkDir;
    }

    /**
     * @param bool $endingSlash
     *
     * @return string
     */
    public function getOriginalVendorDir($endingSlash = false)
    {
        if ($endingSlash && null != $this->originalVendorDir) {
            return $this->originalVendorDir . '/';
        }

        return $this->originalVendorDir;
    }

    /**
     * @return string|null
     */
    public function getSymlinkBasePath()
    {
        return $this->symlinkBasePath;
    }
}


================================================
FILE: src/LEtudiant/Composer/Installer/SharedPackageInstaller.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Installer;

use Composer\Composer;
use Composer\Config;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\Downloader\FilesystemException;
use Composer\Installer\LibraryInstaller;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use LEtudiant\Composer\Data\Package\PackageDataManagerInterface;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Util\SymlinkFilesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SharedPackageInstaller extends LibraryInstaller
{
    const PACKAGE_TYPE = 'shared-package';
    const PACKAGE_PRETTY_NAME = 'letudiant/composer-shared-package-plugin';

    /**
     * @var SharedPackageInstallerConfig
     */
    protected $config;

    /**
     * @var PackageDataManagerInterface
     */
    protected $packageDataManager;

    /**
     * @var SymlinkFilesystem
     */
    protected $filesystem;


    /**
     * @param IOInterface                  $io
     * @param Composer                     $composer
     * @param SymlinkFilesystem            $filesystem
     * @param PackageDataManagerInterface  $dataManager
     * @param SharedPackageInstallerConfig $config
     */
    public function __construct(
        IOInterface $io,
        Composer $composer,
        SymlinkFilesystem $filesystem,
        PackageDataManagerInterface $dataManager,
        SharedPackageInstallerConfig $config
    )
    {
        $this->filesystem = $filesystem;

        parent::__construct($io, $composer, 'library', $this->filesystem);

        $this->config = $config;
        $this->vendorDir = $this->config->getVendorDir();
        $this->packageDataManager = $dataManager;
        $this->packageDataManager->setVendorDir($this->vendorDir);
    }

    /**
     * @inheritdoc
     */
    public function getInstallPath(PackageInterface $package)
    {
        $this->initializeVendorDir();

        $basePath =
            $this->vendorDir . DIRECTORY_SEPARATOR
            . $package->getPrettyName() . DIRECTORY_SEPARATOR
            . $package->getPrettyVersion()
        ;

        $targetDir = $package->getTargetDir();

        return $basePath . ($targetDir ? '/' . $targetDir : '');
    }

    /**
     * @param PackageInterface $package
     *
     * @return string
     */
    protected function getPackageVendorSymlink(PackageInterface $package)
    {
        return $this->config->getSymlinkDir() . DIRECTORY_SEPARATOR . $package->getPrettyName();
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!is_readable($this->getInstallPath($package))) {
            parent::install($repo, $package);
        } elseif (!$repo->hasPackage($package)) {
            $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package));
            $repo->addPackage(clone $package);
        }

        $this->createPackageVendorSymlink($package);
        $this->packageDataManager->addPackageUsage($package);
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     *
     * @return bool
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        // Just check if the sources folder and the link exist
        return
            $repo->hasPackage($package)
            && is_readable($this->getInstallPath($package))
            && is_link($this->getPackageVendorSymlink($package))
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $this->packageDataManager->setPackageInstallationSource($initial);
        $this->packageDataManager->setPackageInstallationSource($target);

        // The package need only a code update because the version (branch), only the commit changed
        if ($this->getInstallPath($initial) === $this->getInstallPath($target)) {
            $this->createPackageVendorSymlink($target);

            parent::update($repo, $initial, $target);
        } else {
            // If the initial package sources folder exists, uninstall it
            $this->composer->getInstallationManager()->uninstall($repo, new UninstallOperation($initial));

            // Install the target package
            $this->composer->getInstallationManager()->install($repo, new InstallOperation($target));
        }
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     *
     * @throws FilesystemException
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if ($this->isSourceDirUnused($package) && $this->io->askConfirmation(
                "The package version <info>" . $package->getPrettyName() . "</info> "
                . "(<fg=yellow>" . $package->getPrettyVersion() . "</fg=yellow>) seems to be unused."
                . PHP_EOL
                . 'Do you want to <fg=red>delete the source folder</fg=red> ? [y/n] (default: no) : ',
                false
            )) {
            $this->packageDataManager->setPackageInstallationSource($package);

            parent::uninstall($repo, $package);
        } else {
            $this->binaryInstaller->removeBinaries($package);
            $repo->removePackage($package);
        }

        $this->packageDataManager->removePackageUsage($package);
        $this->removePackageVendorSymlink($package);
    }

    /**
     * Detect if other project use the dependency by using the "packages.json" file
     *
     * @param PackageInterface $package
     *
     * @return bool
     */
    protected function isSourceDirUnused(PackageInterface $package)
    {
        $usageData = $this->packageDataManager->getPackageUsage($package);

        return sizeof($usageData) <= 1;
    }

    /**
     * @param PackageInterface $package
     */
    protected function createPackageVendorSymlink(PackageInterface $package)
    {
        if ($this->config->isSymlinkEnabled() && $this->filesystem->ensureSymlinkExists(
                $this->getSymlinkSourcePath($package),
                $this->getPackageVendorSymlink($package)
            )
        ) {
            $this->io->write(array(
                '  - Creating symlink for <info>' . $package->getPrettyName()
                . '</info> (<fg=yellow>' . $package->getPrettyVersion() . '</fg=yellow>)',
                ''
            ));
        }
    }

    /**
     * @param PackageInterface $package
     *
     * @return string
     */
    protected function getSymlinkSourcePath(PackageInterface $package)
    {
        if (null != $this->config->getSymlinkBasePath()) {
            $targetDir = $package->getTargetDir();
            $sourcePath =
                $this->config->getSymlinkBasePath()
                . '/' . $package->getPrettyName()
                . '/' . $package->getPrettyVersion()
                . ($targetDir ? '/' . $targetDir : '')
            ;
        } else {
            $sourcePath = $this->getInstallPath($package);
        }

        return $sourcePath;
    }

    /**
     * @param PackageInterface $package
     *
     * @throws FilesystemException
     */
    protected function removePackageVendorSymlink(PackageInterface $package)
    {
        if (
            $this->config->isSymlinkEnabled()
            && $this->filesystem->removeSymlink($this->getPackageVendorSymlink($package))
        ) {
            $this->io->write(array(
                '  - Deleting symlink for <info>' . $package->getPrettyName() . '</info> '
                . '(<fg=yellow>' . $package->getPrettyVersion() . '</fg=yellow>)',
                ''
            ));

            $symlinkParentDirectory = dirname($this->getPackageVendorSymlink($package));
            $this->filesystem->removeEmptyDirectory($symlinkParentDirectory);
        }
    }

    /**
     * @param string $packageType
     *
     * @return bool
     */
    public function supports($packageType)
    {
        return true;
    }
}


================================================
FILE: src/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolver.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Installer\Solver;

use Composer\Config;
use Composer\Downloader\FilesystemException;
use Composer\Installer\InstallerInterface;
use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Util\SymlinkFilesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SharedPackageInstallerSolver implements InstallerInterface
{
    /**
     * @var SymlinkFilesystem
     */
    protected $filesystem;

    /**
     * @var SharedPackageSolver
     */
    protected $solver;

    /**
     * @var SharedPackageInstaller
     */
    protected $symlinkInstaller;

    /**
     * @var LibraryInstaller
     */
    protected $defaultInstaller;


    /**
     * @param SharedPackageSolver    $solver
     * @param SharedPackageInstaller $symlinkInstaller
     * @param LibraryInstaller       $defaultInstaller
     */
    public function __construct(
        SharedPackageSolver $solver,
        SharedPackageInstaller $symlinkInstaller,
        LibraryInstaller $defaultInstaller
    )
    {
        $this->solver           = $solver;
        $this->symlinkInstaller = $symlinkInstaller;
        $this->defaultInstaller = $defaultInstaller;
    }

    /**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     *
     * @return string
     */
    public function getInstallPath(PackageInterface $package)
    {
        if ($this->solver->isSharedPackage($package)) {
            return $this->symlinkInstaller->getInstallPath($package);
        }

        return $this->defaultInstaller->getInstallPath($package);
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if ($this->solver->isSharedPackage($package)) {
            $this->symlinkInstaller->install($repo, $package);
        } else {
            $this->defaultInstaller->install($repo, $package);
        }
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     *
     * @return bool
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if ($this->solver->isSharedPackage($package)) {
            return $this->symlinkInstaller->isInstalled($repo, $package);
        }

        return $this->defaultInstaller->isInstalled($repo, $package);
    }

    /**
     * {@inheritdoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        // If both packages are not shared
        if (!$this->solver->isSharedPackage($initial) && !$this->solver->isSharedPackage($target)) {
            $this->defaultInstaller->update($repo, $initial, $target);
        } else {
            if (!$repo->hasPackage($initial)) {
                throw new \InvalidArgumentException('Package is not installed : ' . $initial->getPrettyName());
            }

            $this->symlinkInstaller->update($repo, $initial, $target);
        }
    }

    /**
     * @param InstalledRepositoryInterface $repo
     * @param PackageInterface             $package
     *
     * @throws FilesystemException
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if ($this->solver->isSharedPackage($package)) {
            if (!$repo->hasPackage($package)) {
                throw new \InvalidArgumentException('Package is not installed : ' . $package->getPrettyName());
            }

            $this->symlinkInstaller->uninstall($repo, $package);
        } else {
            $this->defaultInstaller->uninstall($repo, $package);
        }
    }

    /**
     * @param string $packageType
     *
     * @return bool
     */
    public function supports($packageType)
    {
        // The solving process is in SharedPackageSolver::isSharedPackage() method

        return true;
    }
}


================================================
FILE: src/LEtudiant/Composer/Installer/Solver/SharedPackageSolver.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Installer\Solver;

use Composer\Package\PackageInterface;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\SharedPackageInstaller;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SharedPackageSolver
{
    /**
     * @var array
     */
    protected $packageCallbacks = array();

    /**
     * @var bool
     */
    protected $areAllShared = false;


    /**
     * @param SharedPackageInstallerConfig $config
     */
    public function __construct(SharedPackageInstallerConfig $config)
    {
        $packageList = $config->getPackageList();

        foreach ($packageList as $packageName) {
            if ('*' === $packageName) {
                $this->areAllShared = true;
            }
        }

        if (!$this->areAllShared) {
            $this->packageCallbacks = $this->createCallbacks($packageList);
        }
    }

    /**
     * @param PackageInterface $package
     *
     * @return bool
     */
    public function isSharedPackage(PackageInterface $package)
    {
        $prettyName = $package->getPrettyName();

        // Avoid putting this package into dependencies folder, because on the first installation the package won't be
        // installed in dependencies folder but in the vendor folder.
        // So I prefer keeping this behavior for further installs.
        if (SharedPackageInstaller::PACKAGE_PRETTY_NAME === $prettyName) {
            return false;
        }

        if ($this->areAllShared || SharedPackageInstaller::PACKAGE_TYPE === $package->getType()) {
            return true;
        }

        foreach ($this->packageCallbacks as $equalityCallback) {
            if ($equalityCallback($prettyName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array $packageList
     *
     * @return array
     */
    protected function createCallbacks(array $packageList)
    {
        $callbacks = array();

        foreach ($packageList as $packageName) {
            // Has wild card (*)
            if (false !== strpos($packageName, '*')) {
                $pattern = str_replace('*', '[a-zA-Z0-9-_]+', str_replace('/', '\/', $packageName));

                $callbacks[] = function ($packagePrettyName) use ($pattern) {
                    return 1 === preg_match('/' . $pattern . '/', $packagePrettyName);
                };
            // Raw package name
            } else {
                $callbacks[] = function ($packagePrettyName) use ($packageName) {
                    return $packageName === $packagePrettyName;
                };
            }
        }

        return $callbacks;
    }
}


================================================
FILE: src/LEtudiant/Composer/SharedPackagePlugin.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer;

use Composer\Composer;
use Composer\Installer\LibraryInstaller;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use LEtudiant\Composer\Data\Package\SharedPackageDataManager;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\Solver\SharedPackageSolver;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Installer\Solver\SharedPackageInstallerSolver;
use LEtudiant\Composer\Util\SymlinkFilesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SharedPackagePlugin implements PluginInterface
{
    /**
     * @param Composer    $composer
     * @param IOInterface $io
     */
    public function activate(Composer $composer, IOInterface $io)
    {
        $config = $this->setConfig($composer);

        $composer->getInstallationManager()->addInstaller(new SharedPackageInstallerSolver(
            new SharedPackageSolver($config),
            new SharedPackageInstaller(
                $io,
                $composer,
                new SymlinkFilesystem(),
                new SharedPackageDataManager($composer),
                $config
            ),
            new LibraryInstaller($io, $composer)
        ));
    }

    /**
     * @param Composer $composer
     *
     * @return SharedPackageInstallerConfig
     */
    protected function setConfig(Composer $composer)
    {
        return new SharedPackageInstallerConfig(
            $composer->getConfig()->get('vendor-dir'),
            $composer->getConfig()->get('vendor-dir', 1),
            $composer->getPackage()->getExtra()
        );
    }
}


================================================
FILE: src/LEtudiant/Composer/Util/SymlinkFilesystem.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace LEtudiant\Composer\Util;

use Composer\Util\Filesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 */
class SymlinkFilesystem extends Filesystem
{
    /**
     * Create a symlink
     *
     * @param string $sourcePath
     * @param string $symlinkPath
     *
     * @return bool
     */
    public function ensureSymlinkExists($sourcePath, $symlinkPath)
    {
        if (!is_link($symlinkPath)) {
            $this->ensureDirectoryExists(dirname($symlinkPath));

            return symlink($sourcePath, $symlinkPath);
        }

        return false;
    }

    /**
     * @param string $symlinkPath
     *
     * @return bool
     *
     * @throws \RuntimeException
     */
    public function removeSymlink($symlinkPath)
    {
        if (is_link($symlinkPath)) {
            if (!$this->unlink($symlinkPath)) {
                // @codeCoverageIgnoreStart
                throw new \RuntimeException('Unable to remove the symlink : ' . $symlinkPath);
                // @codeCoverageIgnoreEnd
            }

            return true;
        }

        return false;
    }

    /**
     * @param string $directoryPath
     *
     * @return bool
     *
     * @throws \RuntimeException
     */
    public function removeEmptyDirectory($directoryPath)
    {
        if (is_dir($directoryPath) && $this->isDirEmpty($directoryPath)) {
            if (!$this->removeDirectory($directoryPath)) {
                // @codeCoverageIgnoreStart
                throw new \RuntimeException('Unable to remove the directory : ' . $directoryPath);
                // @codeCoverageIgnoreEnd
            }

            return true;
        }

        return false;
    }
}


================================================
FILE: tests/bootstrap.php
================================================
<?php

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/composer/composer/tests/Composer/TestCase.php';


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Data/Package/SharedPackageDataManagerTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Data\Package;

use Composer\Composer;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use LEtudiant\Composer\Data\Package\SharedPackageDataManager;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Data\Package\SharedPackageDataManager
 */
class SharedPackageDataManagerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var string
     */
    protected $vendorDir;

    /**
     * @var RootPackageInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $rootPackage;

    /**
     * @var Composer
     */
    protected $composer;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->vendorDir = sys_get_temp_dir() . '/composer-test-vendor-shared';
        if (!is_dir($this->vendorDir)) {
            if (!mkdir($this->vendorDir)) {
                throw new \RuntimeException('Cannot create the temporary vendor dir');
            }
        } else {
            $this->clearPackageData();
        }

        $this->composer = new Composer();

        /** @var RootPackageInterface|\PHPUnit_Framework_MockObject_MockObject $rootPackage */
        $this->rootPackage = $this->getMock('Composer\Package\RootPackageInterface');
        $this->composer->setPackage($this->rootPackage);
    }

    /**
     * @inheritdoc
     */
    protected function tearDown()
    {
        $this->clearPackageData();

        parent::tearDown();
    }

    /**
     * @test
     */
    public function getPackageUsageWithoutFile()
    {
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->assertEquals(array(), $dataManager->getPackageUsage($this->createPackage()));
    }

    /**
     * @test
     */
    public function getPackageUsageWithFile()
    {
        $this->initializePackageData();
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->assertEquals(array(
            'letudiant/root-package'
        ), $dataManager->getPackageUsage($this->createPackage()));
    }

    /**
     * @test
     */
    public function addPackageUsageWithoutData()
    {
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->rootPackage
            ->expects($this->once())
            ->method('getName')
            ->willReturn('letudiant/root-package')
        ;

        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->exactly(2))
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->exactly(2))
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->never())
            ->method('getInstallationSource')
        ;

        $dataManager->addPackageUsage($package);

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);

        $content = file_get_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
        $this->assertJson($content);
        $this->assertEquals(array(
            'letudiant/foo-bar/dev-develop' => array(
                'project-usage' => array(
                    'letudiant/root-package'
                )
            )
        ), json_decode($content, true));
    }

    /**
     * @test
     */
    public function addPackageUsageWithHasData()
    {
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->initializePackageData();

        $this->rootPackage
            ->expects($this->once())
            ->method('getName')
            ->willReturn('letudiant/root-package2')
        ;

        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->exactly(2))
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->exactly(2))
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->exactly(0))
            ->method('getInstallationSource')
        ;

        $dataManager->addPackageUsage($package);

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);

        $content = file_get_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
        $this->assertJson($content);
        $this->assertEquals(array(
            'letudiant/foo-bar/dev-develop' => array(
                'installation-source' => SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE,
                'project-usage'       => array(
                    'letudiant/root-package',
                    'letudiant/root-package2'
                )
            )
        ), json_decode($content, true));
    }

    /**
     * @test
     */
    public function removePackageUsageWithoutData()
    {
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->rootPackage
            ->expects($this->once())
            ->method('getName')
            ->willReturn('letudiant/root-package')
        ;

        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->exactly(2))
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->exactly(2))
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->exactly(0))
            ->method('getInstallationSource')
        ;

        $dataManager->removePackageUsage($package);

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);

        $content = file_get_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
        $this->assertJson($content);
        $this->assertEquals(array(), json_decode($content, true));
    }

    /**
     * @test
     */
    public function removePackageUsageWithData()
    {
        $this->initializePackageData();

        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);

        $this->rootPackage
            ->expects($this->exactly(2))
            ->method('getName')
            ->will($this->onConsecutiveCalls(
                'letudiant/root-package',
                'letudiant/root-package2'
            ))
        ;

        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->exactly(4))
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->exactly(4))
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->exactly(0))
            ->method('getInstallationSource')
        ;

        // Remove the right package
        $this->initializePackageData();

        $dataManager->removePackageUsage($package);

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);

        $content = file_get_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
        $this->assertJson($content);
        $this->assertEquals(array(), json_decode($content, true));

        // Remove another package, should not remove the initial package
        $this->initializePackageData();

        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);
        $dataManager->removePackageUsage($package);

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);

        $content = file_get_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
        $this->assertJson($content);
        $this->assertEquals(array(
            'letudiant/foo-bar/dev-develop' => array(
                'installation-source' => SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE,
                'project-usage'       => array(
                    'letudiant/root-package'
                )
            )
        ), json_decode($content, true));
    }

    /**
     * @test
     */
    public function setPackageInstallationSourceWhenNotNull()
    {
        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->once())
            ->method('setInstallationSource')
        ;

        // With already provided installation source
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);
        $dataManager->setPackageInstallationSource($package);
    }

    /**
     * @test
     */
    public function setPackageInstallationSourceWhenNull()
    {
        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->once())
            ->method('setInstallationSource')
            ->with(SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE)
        ;

        $this->initializePackageData();
        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);
        $dataManager->setPackageInstallationSource($package);
    }

    /**
     * @test
     */
    public function setPackageInstallationSourceWhenNullAndNoData()
    {
        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->once())
            ->method('setInstallationSource')
            ->with(SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE)
        ;

        $dataManager = new SharedPackageDataManager($this->composer);
        $dataManager->setVendorDir($this->vendorDir);
        $dataManager->setPackageInstallationSource($package);
    }

    /**
     * Initialize fake data file
     */
    protected function initializePackageData()
    {
        file_put_contents($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME, json_encode(array(
            'letudiant/foo-bar/dev-develop' => array(
                'installation-source' => SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE,
                'project-usage'       => array(
                    'letudiant/root-package'
                )
            )
        )));

        $this->assertFileExists($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME);
    }

    /**
     * Remove the fake data file
     */
    protected function clearPackageData()
    {
        if (is_file($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME)
            && !@unlink($this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME)) {
            throw new \RuntimeException('Cannot delete the file "' . $this->vendorDir . '/' . SharedPackageDataManager::PACKAGE_DATA_FILENAME . '"');
        }
    }

    /**
     * @return PackageInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected function createPackage()
    {
        $package = $this->getMock('Composer\Package\PackageInterface');
        $package
            ->expects($this->once())
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->once())
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        return $package;
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfigTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Installer\Config;

use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\SharedPackageInstaller;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig
 */
class SharedPackageInstallerConfigTest extends \PHPUnit_Framework_TestCase
{
    /**
     * Delete both env vars
     */
    protected function tearDown()
    {
        putenv(SharedPackageInstallerConfig::ENV_PARAMETER_VENDOR_DIR);
        putenv(SharedPackageInstallerConfig::ENV_PARAMETER_SYMLINK_BASE_PATH);

        parent::tearDown();
    }

    /**
     * @test
     *
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage The "vendor-dir" parameter for "shared-package" configuration should be provided in your project composer.json ("extra" key)
     */
    public function noVendorDirConfigured()
    {
        $this->createInstallerConfig(array());
    }

    /**
     * @test
     *
     * @dataProvider getVendorDirDataProvider
     *
     * @param string $vendorDirPath
     */
    public function getVendorDir($vendorDirPath)
    {
        $this->assertEquals(sys_get_temp_dir() . '/composer-test-dependencies-dir', $this->createInstallerConfig(array(
            'vendor-dir' => $vendorDirPath
        ))->getVendorDir());
    }

    /**
     * @test
     *
     * @dataProvider getVendorDirFromEnvVarDataProvider
     *
     * @param string $vendorDirPath
     * @param string $envVar
     */
    public function getVendorDirFromEnvVar($vendorDirPath, $envVar)
    {
        putenv(SharedPackageInstallerConfig::ENV_PARAMETER_VENDOR_DIR . '=' . $envVar);

        $this->assertEquals(sys_get_temp_dir() . '/composer-test-dependencies-dir-env-var', $this->createInstallerConfig(array(
            'vendor-dir' => $vendorDirPath
        ))->getVendorDir());
    }

    /**
     * @return array
     */
    public function getVendorDirFromEnvVarDataProvider()
    {
        return array(
            array(
                sys_get_temp_dir() . '/composer-test-dependencies-dir',
                sys_get_temp_dir() . '/composer-test-dependencies-dir-env-var'
            ),
            array(
                'composer-test-dependencies-dir',
                'composer-test-dependencies-dir-env-var'
            )
        );
    }

    /**
     * @return array
     */
    public function getVendorDirDataProvider()
    {
        return array(
            array(
                sys_get_temp_dir() . '/composer-test-dependencies-dir'
            ),
            array(
                'composer-test-dependencies-dir'
            )
        );
    }

    /**
     * @test
     *
     * @dataProvider getSymlinkDirDataProvider
     *
     * @param string $symlinkDirPath
     */
    public function getSymlinkDir($symlinkDirPath)
    {
        $this->assertEquals(sys_get_temp_dir() . '/composer-test-vendor-shared-dir', $this->createInstallerConfig(array(
            'vendor-dir'  => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-dir' => $symlinkDirPath
        ))->getSymlinkDir());
    }

    /**
     * @return array
     */
    public function getSymlinkDirDataProvider()
    {
        return array(
            array(
                sys_get_temp_dir() . '/composer-test-vendor-shared-dir'
            ),
            array(
                'composer-test-vendor-shared-dir'
            )
        );
    }

    /**
     * @test
     */
    public function getSymlinkDirWithEmptyConfiguration()
    {
        $this->assertEquals(sys_get_temp_dir() . '/vendor-shared', $this->createInstallerConfig(array(
            'vendor-dir'  => sys_get_temp_dir() . '/composer-test-dependencies-dir'
        ))->getSymlinkDir());
    }

    /**
     * @test
     *
     * @dataProvider getOriginalVendorDirDataProvider
     */
    public function getOriginalVendorDir($expectedValue, $nullable)
    {
        $this->assertEquals($expectedValue, $this->createInstallerConfig(array(
            'vendor-dir'  => sys_get_temp_dir() . '/composer-test-dependencies-dir'
        ))->getOriginalVendorDir($nullable));
    }

    /**
     * @return array
     */
    public function getOriginalVendorDirDataProvider()
    {
        return array(
            array(
                'composer-test-vendor-dir/',
                true
            ),
            array(
                'composer-test-vendor-dir',
                false
            )
        );
    }

    /**
     * @test
     */
    public function getSymlinkBasePathWhenNull()
    {
        $this->assertNull($this->createInstallerConfig(array(
            'vendor-dir'  => sys_get_temp_dir() . '/composer-test-dependencies-dir'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function getSymlinkBasePathWhenNotNull()
    {
        $this->assertEquals('/foo/bar', $this->createInstallerConfig(array(
            'vendor-dir'        => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-base-path' => '/foo/bar'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function getSymlinkBasePathWhenNotNullAndEndingSlash()
    {
        $this->assertEquals('/foo/bar', $this->createInstallerConfig(array(
            'vendor-dir'        => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-base-path' => '/foo/bar/'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function getSymlinkBasePathWhenVendorDirIsRelative()
    {
        $this->assertEquals('../../../composer-test-dependencies-dir', $this->createInstallerConfig(array(
            'vendor-dir' => '../composer-test-dependencies-dir'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function getSymlinkBasePathWhenRelative()
    {
        $this->assertEquals('../../../composer-test-dependencies-dir', $this->createInstallerConfig(array(
            'vendor-dir'        => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-base-path' => '../composer-test-dependencies-dir'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function getSymlinkBasePathFromEnvVar()
    {
        putenv(SharedPackageInstallerConfig::ENV_PARAMETER_SYMLINK_BASE_PATH . '=/composer-test-dependencies-dir-env-var');

        $this->assertEquals('/composer-test-dependencies-dir-env-var', $this->createInstallerConfig(array(
            'vendor-dir'        => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-base-path' => '../composer-test-dependencies-dir'
        ))->getSymlinkBasePath());
    }

    /**
     * @test
     */
    public function isSymlinkEnabledDefaultValue()
    {
        $this->assertTrue($this->createInstallerConfig(array(
            'vendor-dir' => sys_get_temp_dir() . '/composer-test-dependencies-dir',
        ))->isSymlinkEnabled());
    }

    /**
     * @test
     */
    public function setIsSymlinkEnabled()
    {
        $this->assertFalse($this->createInstallerConfig(array(
            'vendor-dir'      => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-enabled' => false
        ))->isSymlinkEnabled());
    }

    /**
     * @test
     *
     * @expectedException \UnexpectedValueException
     * @expectedExceptionMessage The configuration "symlink-enabled" should be a boolean
     */
    public function setIsSymlinkEnabledWithString()
    {
        $this->assertTrue($this->createInstallerConfig(array(
            'vendor-dir'      => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'symlink-enabled' => 'foo'
        ))->isSymlinkEnabled());
    }

    /**
     * @test
     *
     * @expectedException \UnexpectedValueException
     * @expectedExceptionMessage The configuration "package-list" should be a JSON object
     */
    public function setPackageListWrongTypeException()
    {
        $this->createInstallerConfig(array(
            'vendor-dir'   => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'package-list' => 'foo'
        ));
    }

    /**
     * @test
     */
    public function setPackageList()
    {
        $this->assertEquals(array('foo'), $this->createInstallerConfig(array(
            'vendor-dir'   => sys_get_temp_dir() . '/composer-test-dependencies-dir',
            'package-list' => array(
                'foo'
            )
        ))->getPackageList());
    }

    /**
     * @test
     */
    public function setPackageListEmpty()
    {
        $this->assertEquals(array(), $this->createInstallerConfig(array(
            'vendor-dir' => sys_get_temp_dir() . '/composer-test-dependencies-dir'
        ))->getPackageList());
    }

    /**
     * @param array       $extra
     * @param string      $relativeDir
     * @param null|string $absoluteDir
     *
     * @return SharedPackageInstallerConfig
     */
    protected function createInstallerConfig(array $extra, $relativeDir = 'composer-test-vendor-dir', $absoluteDir = null)
    {
        if (null == $absoluteDir) {
            $absoluteDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $relativeDir;
        }

        return new SharedPackageInstallerConfig(
            $relativeDir,
            $absoluteDir,
            array(
                SharedPackageInstaller::PACKAGE_TYPE => $extra
            )
        );
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/SharedPackageInstallerTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Installer;

use Composer\Composer;
use Composer\Config;
use Composer\Downloader\DownloadManager;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\Package;
use Composer\Package\RootPackage;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\TestCase;
use Composer\Util\Filesystem;
use LEtudiant\Composer\Data\Package\PackageDataManagerInterface;
use LEtudiant\Composer\Data\Package\SharedPackageDataManager;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Util\SymlinkFilesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Installer\SharedPackageInstaller
 */
class SharedPackageInstallerTest extends TestCase
{
    /**
     * @var Composer
     */
    protected $composer;

    /**
     * @var SharedPackageInstallerConfig
     */
    protected $config;

    /**
     * @var string
     */
    protected $vendorDir;

    /**
     * @var string
     */
    protected $binDir;

    /**
     * @var string
     */
    protected $symlinkDir;

    /**
     * @var string
     */
    protected $dependenciesDir;

    /**
     * @var DownloadManager|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $dm;

    /**
     * @var InstalledRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $repository;

    /**
     * @var IOInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $io;

    /**
     * @var SymlinkFilesystem
     */
    protected $fs;

    /**
     * @var PackageDataManagerInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $dataManager;

    /**
     * @var InstallationManager|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $im;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->fs = new SymlinkFilesystem();
        $this->composer = new Composer();
        $composerConfig = new Config();

        $this->composer->setConfig($composerConfig);

        $this->im = $this->getMock('Composer\Installer\InstallationManager');
        $this->composer->setInstallationManager($this->im);

        $this->vendorDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-vendor';
        $this->ensureDirectoryExistsAndClear($this->vendorDir);
        $this->binDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-bin';
        $this->ensureDirectoryExistsAndClear($this->binDir);
        $this->dependenciesDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'composer-test-dependencies';
        $this->ensureDirectoryExistsAndClear($this->dependenciesDir);
        $this->symlinkDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'composer-test-vendor-shared';

        $composerConfig->merge(array(
            'config' => array(
                'vendor-dir' => $this->vendorDir,
                'bin-dir'    => $this->binDir,
            ),
        ));

        $this->dm = $this->getMockBuilder('Composer\Downloader\DownloadManager')
            ->disableOriginalConstructor()
            ->getMock()
        ;
        $this->composer->setDownloadManager($this->dm);

        $extraConfig = array(
            SharedPackageInstaller::PACKAGE_TYPE => array(
                'vendor-dir'  => $this->dependenciesDir,
                'symlink-dir' => $this->symlinkDir
            )
        );

        /** @var RootPackage|\PHPUnit_Framework_MockObject_MockObject $package */
        $package = $this->getMock('Composer\Package\RootPackageInterface');
        $package
            ->expects($this->any())
            ->method('getExtra')
            ->willReturn($extraConfig)
        ;
        $this->composer->setPackage($package);

        $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
        $this->io = $this->getMock('Composer\IO\IOInterface');

        $this->dataManager = $this->getMockBuilder('LEtudiant\Composer\Data\Package\SharedPackageDataManager')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $vendorDirParams = explode(DIRECTORY_SEPARATOR, $this->vendorDir);
        $this->config = new SharedPackageInstallerConfig(
            end($vendorDirParams),
            $this->vendorDir,
            $extraConfig
        );
    }

    /**
     * @inheritdoc
     */
    protected function tearDown()
    {
        $this->fs->removeDirectory($this->vendorDir);
        $this->fs->removeDirectory($this->binDir);
        $this->fs->removeDirectory($this->symlinkDir);
        $this->fs->removeDirectory($this->dependenciesDir);

        parent::tearDown();
    }

    /**
     * @test
     */
    public function testInstallerCreationShouldNotCreateVendorDirectory()
    {
        $this->fs->removeDirectory($this->vendorDir);
        $this->createInstaller();
        $this->assertFileNotExists($this->vendorDir);
    }

    /**
     * @test
     */
    public function testInstallerCreationShouldNotCreateBinDirectory()
    {
        $this->fs->removeDirectory($this->binDir);
        $this->createInstaller();
        $this->assertFileNotExists($this->binDir);
    }

    /**
     * @test
     */
    public function isInstalled()
    {
        $installer = $this->createInstaller();
        $package = $this->createPackageMock();
        $this->repository
            ->expects($this->exactly(2))
            ->method('hasPackage')
            ->with($package)
            ->will($this->onConsecutiveCalls(false, true))
        ;

        $this->assertFalse($installer->isInstalled($this->repository, $package));

        $this->fs->ensureDirectoryExists($installer->getInstallPath($package));
        $reflection = new \ReflectionObject($installer);
        $method = $reflection->getMethod('createPackageVendorSymlink');
        $method->setAccessible(true);
        $method->invokeArgs($installer, array($package));

        $this->assertTrue($installer->isInstalled($this->repository, $package));
    }

    /**
     * @test
     */
    public function install()
    {
        $installer = $this->createInstaller();
        $package = $this->createPackageMock();

        $this->dm
            ->expects($this->exactly(1))
            ->method('download')
            ->with($package, $this->dependenciesDir . '/letudiant/foo-bar/dev-develop')
        ;

        $this->repository
            ->expects($this->exactly(2))
            ->method('addPackage')
            ->with($package)
        ;

        $this->dataManager
            ->expects($this->exactly(2))
            ->method('addPackageUsage')
            ->willReturn($package)
        ;

        $installer->install($this->repository, $package);

        $this->assertFileExists($this->vendorDir, 'Vendor dir should be created');
        $this->assertFileExists($this->binDir, 'Bin dir should be created');
        $this->assertFileExists($this->symlinkDir, 'Symlink dir should be created');
        $this->assertFileExists($this->symlinkDir . '/letudiant', 'Symlink package prefix dir should be created');
        $this->assertTrue(is_link($this->symlinkDir . '/letudiant/foo-bar'), 'Symlink should be created');
        $this->assertFileExists($this->dependenciesDir, 'Dependencies dir should be created');

        // Install another time with already created directory
        $this->fs->ensureDirectoryExists($installer->getInstallPath($package));
        $this->repository
            ->expects($this->once())
            ->method('hasPackage')
            ->with($package)
            ->willReturn(false)
        ;

        $installer->install($this->repository, $package);
    }

    /**
     * @test
     *
     * @depends install
     */
    public function installWithSymlinkBasePath()
    {
        $symlinkBasePath = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'composer-test-symlink-base-path';

        $config = $this->createConfigMock($this->dependenciesDir, $this->symlinkDir, $symlinkBasePath);
        $installer = $this->createInstaller($config);
        $package = $this->createPackageMock();

        $installer->install($this->repository, $package);

        $this->assertFileExists($this->symlinkDir, 'Symlink dir should be created');
        $this->assertFileExists($this->symlinkDir . '/letudiant', 'Symlink package prefix dir should be created');
        $this->assertTrue(is_link($this->symlinkDir . '/letudiant/foo-bar'), 'Symlink should be created');

        $this->assertEquals($symlinkBasePath . '/letudiant/foo-bar/dev-develop', readlink($this->symlinkDir . '/letudiant/foo-bar'), 'Symlink should have a custom base path');
    }

    /**
     * @test
     *
     * @depends install
     */
    public function installWithSymlinkBasePathAndTargetDir()
    {
        $symlinkBasePath = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'composer-test-symlink-base-path';

        $config = $this->createConfigMock($this->dependenciesDir, $this->symlinkDir, $symlinkBasePath);
        $installer = $this->createInstaller($config);
        $package = $this->createPackageMock();
        $package
            ->expects($this->exactly(5))
            ->method('getTargetDir')
            ->willReturn('target-dir')
        ;

        $installer->install($this->repository, $package);

        $this->assertFileExists($this->symlinkDir, 'Symlink dir should be created');
        $this->assertFileExists($this->symlinkDir . '/letudiant', 'Symlink package prefix dir should be created');
        $this->assertTrue(is_link($this->symlinkDir . '/letudiant/foo-bar'), 'Symlink should be created');

        $this->assertEquals($symlinkBasePath . '/letudiant/foo-bar/dev-develop/target-dir', readlink($this->symlinkDir . '/letudiant/foo-bar'), 'Symlink should have a custom base path');
    }

    /**
     * @test
     *
     * @depends install
     */
    public function installWithSymlinkDisabled()
    {
        $config = $this->createConfigMock($this->dependenciesDir, $this->symlinkDir, null, false);
        $installer = $this->createInstaller($config);
        $package = $this->createPackageMock();

        $installer->install($this->repository, $package);

        $this->assertFileNotExists($this->symlinkDir, 'Symlink dir should be created');
    }

    /**
     * @test
     *
     * @depends testInstallerCreationShouldNotCreateVendorDirectory
     * @depends testInstallerCreationShouldNotCreateBinDirectory
     */
    public function updateCode()
    {
        $installer = $this->createInstaller();

        $initial = $this->createPackageMock();
        $target  = $this->createPackageMock();

        $this->dm
            ->expects($this->never())
            ->method('download')
        ;

        $this->repository
            ->expects($this->exactly(2))
            ->method('hasPackage')
            ->will($this->onConsecutiveCalls(true, true, false))
        ;

        $this->dataManager
            ->expects($this->never())
            ->method('addPackageUsage')
        ;

        $this->dataManager
            ->expects($this->never())
            ->method('removePackageUsage')
        ;

        $installer->update($this->repository, $initial, $target);

        $this->assertFileExists($this->vendorDir, 'Vendor dir should be created');
        $this->assertFileExists($this->binDir, 'Bin dir should be created');
        $this->assertFileExists($this->dependenciesDir, 'Dependencies dir should be created');
        $this->assertFileExists($this->symlinkDir, 'Symlink dir should be created');

        $this->assertTrue(is_link($this->symlinkDir . '/letudiant/foo-bar'));
    }

    /**
     * @test
     *
     * @depends testInstallerCreationShouldNotCreateVendorDirectory
     * @depends testInstallerCreationShouldNotCreateBinDirectory
     */
    public function updateFull()
    {
        /** @var InstallationManager|\PHPUnit_Framework_MockObject_MockObject $im */
        $this->im
            ->expects($this->once())
            ->method('uninstall')
        ;

        $this->im
            ->expects($this->once())
            ->method('install')
        ;

        $installer = $this->createInstaller();

        $initial = $this->createPackageMock('letudiant/bar-foo');
        $target  = $this->createPackageMock();

        $installer->update($this->repository, $initial, $target);
    }

    /**
     * @test
     */
    public function uninstall()
    {
        $this->io
            ->expects($this->once())
            ->method('askConfirmation')
            ->willReturn(true)
        ;

        /** @var SymlinkFilesystem|\PHPUnit_Framework_MockObject_MockObject $filesystem */
        $filesystem = $this->getMock('\LEtudiant\Composer\Util\SymlinkFilesystem');
        $filesystem
            ->expects($this->once())
            ->method('removeSymlink')
            ->willReturn(true)
        ;

        $installer = $this->createInstaller($this->config, $filesystem);
        $package = $this->createPackageMock();

        $this->repository
            ->expects($this->exactly(1))
            ->method('hasPackage')
            ->with($package)
            ->will($this->onConsecutiveCalls(true, true))
        ;

        $this->repository
            ->expects($this->once())
            ->method('removePackage')
            ->with($package)
        ;

        $this->dm
            ->expects($this->once())
            ->method('remove')
            ->with($package, $this->dependenciesDir . '/letudiant/foo-bar/dev-develop')
        ;

        $this->dataManager
            ->expects($this->once())
            ->method('removePackageUsage')
            ->with($package)
        ;

        $installer->uninstall($this->repository, $package);
    }

    /**
     * @test
     */
    public function uninstallKeepSources()
    {
        $this->io
            ->expects($this->once())
            ->method('askConfirmation')
            ->willReturn(false)
        ;

        /** @var SymlinkFilesystem|\PHPUnit_Framework_MockObject_MockObject $filesystem */
        $filesystem = $this->getMock('\LEtudiant\Composer\Util\SymlinkFilesystem');
        $filesystem
            ->expects($this->once())
            ->method('removeSymlink')
            ->willReturn(true)
        ;

        $installer = $this->createInstaller($this->config, $filesystem);
        $package = $this->createPackageMock();

        $this->repository
            ->expects($this->once())
            ->method('removePackage')
            ->with($package)
        ;

        $this->dm
            ->expects($this->never())
            ->method('remove')
        ;

        $this->dataManager
            ->expects($this->once())
            ->method('removePackageUsage')
            ->with($package)
        ;

        $installer->uninstall($this->repository, $package);
    }

    /**
     * @test
     */
    public function getInstallPath()
    {
        $installer = $this->createInstaller();
        $package = $this->createPackageMock();
        $package
            ->expects($this->once())
            ->method('getTargetDir')
            ->will($this->returnValue(null));
        $this->assertEquals($this->dependenciesDir . '/letudiant/foo-bar/dev-develop', $installer->getInstallPath($package));
    }

    /**
     * @test
     */
    public function getInstallPathWithTargetDir()
    {
        $installer = $this->createInstaller();
        $package = $this->createPackageMock();
        $package
            ->expects($this->once())
            ->method('getTargetDir')
            ->will($this->returnValue('Some/Namespace'))
        ;

        $package
            ->expects($this->any())
            ->method('getPrettyName')
            ->will($this->returnValue('foo/bar'))
        ;

        $this->assertEquals($this->dependenciesDir . '/letudiant/foo-bar/dev-develop/Some/Namespace', $installer->getInstallPath($package));
    }

    /**
     * @test
     */
    public function supports()
    {
        $installer = $this->createInstaller();

        $this->assertTrue($installer->supports('library'));
        $this->assertTrue($installer->supports(SharedPackageInstaller::PACKAGE_TYPE));
    }

    /**
     * @param string|null $vendorDir
     * @param string|null $symlinkDir
     * @param string|null $symlinkBasePath
     * @param bool        $isSymlinkEnabled
     *
     * @return SharedPackageInstallerConfig|\PHPUnit_Framework_MockObject_MockObject
     */
    protected function createConfigMock($vendorDir = null, $symlinkDir = null, $symlinkBasePath = null, $isSymlinkEnabled = true)
    {
        if (null == $vendorDir) {
            $vendorDir = $this->dependenciesDir;
        }

        if (null == $symlinkDir) {
            $symlinkDir = $this->symlinkDir;
        }

        $config = $this->getMockBuilder('LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $config
            ->expects($this->any())
            ->method('getVendorDir')
            ->willReturn($vendorDir)
        ;

        $config
            ->expects($this->any())
            ->method('getSymlinkDir')
            ->willReturn($symlinkDir)
        ;

        $config
            ->expects($this->any())
            ->method('getSymlinkBasePath')
            ->willReturn($symlinkBasePath)
        ;

        $config
            ->expects($this->any())
            ->method('isSymlinkEnabled')
            ->willReturn($isSymlinkEnabled)
        ;

        $config
            ->expects($this->any())
            ->method('getOriginalVendorDir')
            ->with(array(true))
            ->willReturn($this->vendorDir . '/')
        ;

        $config
            ->expects($this->any())
            ->method('getOriginalVendorDir')
            ->with(array(false))
            ->willReturn($this->vendorDir)
        ;

        return $config;
    }

    /**
     * @param string $prettyName
     *
     * @return Package|\PHPUnit_Framework_MockObject_MockObject
     */
    protected function createPackageMock($prettyName = 'letudiant/foo-bar')
    {
        /** @var Package|\PHPUnit_Framework_MockObject_MockObject $package */
        $package = $this->getMockBuilder('Composer\Package\Package')
            ->setConstructorArgs(array(md5(mt_rand()), 'dev-develop', 'dev-develop'))
            ->getMock()
        ;

        $package
            ->expects($this->any())
            ->method('getType')
            ->willReturn(SharedPackageInstaller::PACKAGE_TYPE)
        ;

        $package
            ->expects($this->any())
            ->method('getPrettyName')
            ->willReturn($prettyName)
        ;

        $package
            ->expects($this->any())
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->any())
            ->method('getVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->any())
            ->method('getInstallationSource')
            ->willReturn(SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE)
        ;

        return $package;
    }

    /**
     * @param null|SharedPackageInstallerConfig $config
     * @param null|Filesystem                   $filesystem
     *
     * @return SharedPackageInstaller
     */
    protected function createInstaller($config = null, $filesystem = null)
    {
        if (null == $filesystem) {
            $filesystem = $this->fs;
        }

        if (null == $config) {
            $config = $this->config;
        }

        return new SharedPackageInstaller($this->io, $this->composer, $filesystem, $this->dataManager, $config);
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverNotSharedTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Installer\Solver;

use Composer\Installer\LibraryInstaller;
use Composer\Package\Package;
use Composer\Repository\InstalledRepositoryInterface;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\Solver\SharedPackageSolver;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Installer\Solver\SharedPackageInstallerSolver;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Installer\Solver\SharedPackageInstallerSolver
 */
class SharedPackageInstallerSolverNotSharedTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var LibraryInstaller|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $installer;

    /**
     * @var InstalledRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $repository;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->installer = $this->getMockBuilder('\Composer\Installer\LibraryInstaller')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $this->repository = $this->getMock('\Composer\Repository\InstalledRepositoryInterface');
    }

    /**
     * @test
     */
    public function getInstallPath()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('getInstallPath')
            ->with($package)
        ;

        $this->createSolver()->getInstallPath($package);
    }

    /**
     * @test
     */
    public function install()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('install')
            ->with($this->repository, $package)
        ;

        $this->createSolver()->install($this->repository, $package);
    }

    /**
     * @test
     */
    public function isInstalled()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('isInstalled')
            ->with($this->repository, $package)
        ;

        $this->createSolver()->isInstalled($this->repository, $package);
    }

    /**
     * @test
     */
    public function update()
    {
        $initial = $this->createPackageMock();
        $target = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('update')
            ->with($this->repository, $initial, $target)
        ;

        $this->createSolver()->update($this->repository, $initial, $target);
    }

    /**
     * @test
     */
    public function uninstall()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('uninstall')
            ->with($this->repository, $package)
        ;

        $this->createSolver()->uninstall($this->repository, $package);
    }

    /**
     * @test
     */
    public function supports()
    {
        $this->assertTrue($this->createSolver()->supports('library'));
    }

    /**
     * @return SharedPackageInstallerSolver
     */
    protected function createSolver()
    {
        /** @var SharedPackageInstaller|\PHPUnit_Framework_MockObject_MockObject $symlinkInstaller */
        $symlinkInstaller = $this->getMockBuilder('\LEtudiant\Composer\Installer\SharedPackageInstaller')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $config = new SharedPackageInstallerConfig('foo', 'bar', array(
            SharedPackageInstaller::PACKAGE_TYPE => array(
                'vendor-dir' => 'foo'
            )
        ));

        return new SharedPackageInstallerSolver(new SharedPackageSolver($config), $symlinkInstaller, $this->installer);
    }

    /**
     * @return Package|\PHPUnit_Framework_MockObject_MockObject
     */
    protected function createPackageMock()
    {
        return $this->getMockBuilder('Composer\Package\Package')
            ->setConstructorArgs(array(md5(mt_rand()), '1.0.0.0', '1.0.0'))
            ->getMock()
        ;
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverSharedTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Installer\Solver;

use Composer\Installer\LibraryInstaller;
use Composer\Package\Package;
use Composer\Repository\InstalledRepositoryInterface;
use LEtudiant\Composer\Data\Package\SharedPackageDataManager;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\Solver\SharedPackageSolver;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Installer\Solver\SharedPackageInstallerSolver;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Installer\Solver\SharedPackageInstallerSolver
 */
class SharedPackageInstallerSolverSharedTest extends SharedPackageInstallerSolverNotSharedTest
{
    /**
     * @var SharedPackageInstaller|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $installer;

    /**
     * @var InstalledRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $repository;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->installer = $this->getMockBuilder('\LEtudiant\Composer\Installer\SharedPackageInstaller')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $this->repository = $this->getMock('\Composer\Repository\InstalledRepositoryInterface');
    }

    /**
     * @return SharedPackageInstallerSolver
     */
    protected function createSolver()
    {
        /** @var LibraryInstaller|\PHPUnit_Framework_MockObject_MockObject $defaultInstaller */
        $defaultInstaller = $this->getMockBuilder('\Composer\Installer\LibraryInstaller')
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $config = new SharedPackageInstallerConfig('foo', 'bar', array(
            SharedPackageInstaller::PACKAGE_TYPE => array(
                'vendor-dir' => 'foo'
            )
        ));

        return new SharedPackageInstallerSolver(new SharedPackageSolver($config), $this->installer, $defaultInstaller);
    }

    /**
     * @test
     *
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Package is not installed : letudiant/foo-bar
     */
    public function updateWhenInitialNotInstalledException()
    {
        $initial = $this->createPackageMock();
        $target = $this->createPackageMock();

        $this->installer
            ->expects($this->never())
            ->method('update')
            ->with($this->repository, $initial, $target)
        ;

        $this->createSolver()->update($this->repository, $initial, $target);
    }

    /**
     * @test
     */
    public function uninstall()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('uninstall')
            ->with($this->repository, $package)
        ;

        $this->repository
            ->expects($this->once())
            ->method('hasPackage')
            ->with($package)
            ->willReturn(true)
        ;

        $this->createSolver()->uninstall($this->repository, $package);
    }

    /**
     * @test
     */
    public function update()
    {
        $initial = $this->createPackageMock();
        $target = $this->createPackageMock();

        $this->installer
            ->expects($this->once())
            ->method('update')
            ->with($this->repository, $initial, $target)
        ;

        $this->repository
            ->expects($this->once())
            ->method('hasPackage')
            ->with($initial)
            ->willReturn(true)
        ;

        $this->createSolver()->update($this->repository, $initial, $target);
    }

    /**
     * @test
     *
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Package is not installed : letudiant/foo-bar
     */
    public function uninstallWhenPackageNotInstalledException()
    {
        $package = $this->createPackageMock();

        $this->installer
            ->expects($this->never())
            ->method('uninstall')
            ->with($this->repository, $package)
        ;

        $this->createSolver()->uninstall($this->repository, $package);
    }

    /**
     * @test
     */
    public function supports()
    {
        $this->assertTrue($this->createSolver()->supports(SharedPackageInstaller::PACKAGE_TYPE));
    }

    /**
     * @return Package|\PHPUnit_Framework_MockObject_MockObject
     */
    protected function createPackageMock()
    {
        /** @var Package|\PHPUnit_Framework_MockObject_MockObject $package */
        $package = $this->getMockBuilder('Composer\Package\Package')
            ->setConstructorArgs(array(md5(mt_rand()), 'dev-develop', 'dev-develop'))
            ->getMock()
        ;

        $package
            ->expects($this->any())
            ->method('getType')
            ->willReturn(SharedPackageInstaller::PACKAGE_TYPE)
        ;

        $package
            ->expects($this->any())
            ->method('isDev')
            ->willReturn(true)
        ;

        $package
            ->expects($this->any())
            ->method('getPrettyName')
            ->willReturn('letudiant/foo-bar')
        ;

        $package
            ->expects($this->any())
            ->method('getPrettyVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->any())
            ->method('getVersion')
            ->willReturn('dev-develop')
        ;

        $package
            ->expects($this->any())
            ->method('getInstallationSource')
            ->willReturn(SharedPackageDataManager::PACKAGE_INSTALLATION_SOURCE)
        ;

        return $package;
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageSolverTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Installer\Solver;

use Composer\Package\PackageInterface;
use LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\Installer\Solver\SharedPackageSolver;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Installer\Solver\SharedPackageSolver
 */
class SharedPackageSolverTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var SharedPackageInstallerConfig|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $config;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->config = $this->getMockBuilder('LEtudiant\Composer\Installer\Config\SharedPackageInstallerConfig')
            ->disableOriginalConstructor()
            ->getMock()
        ;
    }

    /**
     * @test
     */
    public function constructAllShared()
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array(
                '*'
            ))
        ;

        $solver = $this->createSolver();

        $this->assertAttributeEquals(true, 'areAllShared', $solver, 'All packages should be shared');
        $this->assertAttributeCount(0, 'packageCallbacks', $solver, 'Package callbacks should be empty');
    }

    /**
     * @test
     */
    public function constructWithPackageList()
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array(
                'foo/bar',
                'bar/*'
            ))
        ;

        $solver = $this->createSolver();

        $this->assertAttributeEquals(false, 'areAllShared', $solver, 'All packages should not be shared');
        $this->assertAttributeCount(2, 'packageCallbacks', $solver, 'Package callbacks should be filled');

        $callbacks = $this->getObjectAttribute($solver, 'packageCallbacks');
        $this->assertArrayHasKey(0, $callbacks);
        $this->assertInternalType('callable', $callbacks[0]);
        $this->assertInternalType('callable', $callbacks[1]);
    }

    /**
     * @test
     */
    public function constructWhenNull()
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array())
        ;

        $solver = $this->createSolver();

        $this->assertAttributeEquals(false, 'areAllShared', $solver, 'All packages should not be shared');
        $this->assertAttributeCount(0, 'packageCallbacks', $solver, 'Package callbacks should be empty');
    }

    /**
     * @param int    $i
     * @param bool   $expectedValue
     * @param string $packagePrettyName
     *
     * @test
     * @dataProvider createCallbacksDataProvider
     */
    public function createCallbacks($i, $expectedValue, $packagePrettyName)
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array(
                'foo/bar',
                'bar/*'
            ))
        ;

        $solver = $this->createSolver();
        $callbacks = $this->getObjectAttribute($solver, 'packageCallbacks');

        $this->assertEquals($expectedValue, $callbacks[$i]($packagePrettyName));
    }

    /**
     * @return array
     */
    public function createCallbacksDataProvider()
    {
        return array(
            // Raw equality (foo/bar)
            array(0, false, 'foo/bar2'),
            array(0, false, 'foo2/bar2'),
            array(0, false, 'foo2/bar'),
            array(0, false, 'foo/'),
            array(0, false, 'foo'),
            array(0, false, ''),
            array(0, true, 'foo/bar'),

            // Regex equality (bar/*)
            array(1, false, 'foo/bar'),
            array(1, false, 'bar2/foo'),
            array(1, false, 'bar/'),
            array(1, false, 'bar'),
            array(1, true, 'bar/foo'),
            array(1, true, 'bar/foo2'),
            array(1, true, 'bar/foo-2'),
            array(1, true, 'bar/foo_2'),
            array(1, true, 'bar/foO_2'),
        );
    }

    /**
     * @test
     */
    public function isSharedPackageWithAllShared()
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array(
                '*'
            ))
        ;

        $solver = $this->createSolver();

        // False
        $this->assertFalse($solver->isSharedPackage($this->createPackageMock(SharedPackageInstaller::PACKAGE_PRETTY_NAME)));

        // True
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock()));
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('foo/bar')));
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('bar/foo')));
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('unknown/unknown')));
    }

    /**
     * @test
     */
    public function isSharedPackageWithPackageList()
    {
        $this->config
            ->expects($this->once())
            ->method('getPackageList')
            ->willReturn(array(
                'foo/bar',
                'bar/*'
            ))
        ;

        $solver = $this->createSolver();

        // False
        $this->assertFalse($solver->isSharedPackage($this->createPackageMock('unknown/unknown')));

        // True

        // Package with "shared-package" type
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('unknown/unknown', SharedPackageInstaller::PACKAGE_TYPE)));

        // Packages in the package list
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('foo/bar')));
        $this->assertTrue($solver->isSharedPackage($this->createPackageMock('bar/foo')));
    }

    /**
     * @param null|string $prettyName
     * @param null|string $type
     *
     * @return PackageInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    public function createPackageMock($prettyName = null, $type = null)
    {
        $package = $this->getMock('Composer\Package\PackageInterface');

        if (null != $prettyName) {
            $package
                ->expects($this->once())
                ->method('getPrettyName')
                ->willReturn($prettyName)
            ;
        }

        if (null != $type) {
            $package
                ->expects($this->once())
                ->method('getType')
                ->willReturn($type)
            ;
        }

        return $package;
    }

    /**
     * @return SharedPackageSolver
     */
    protected function createSolver()
    {
        return new SharedPackageSolver($this->config);
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/SharedPackagePluginTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer;

use Composer\Composer;
use Composer\Config;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;
use LEtudiant\Composer\Installer\SharedPackageInstaller;
use LEtudiant\Composer\SharedPackagePlugin;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\SharedPackagePlugin
 */
class SharedPackagePluginTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Composer
     */
    protected $composer;

    /**
     * @var IOInterface|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $io;

    /**
     * @var InstallationManager|\PHPUnit_Framework_MockObject_MockObject
     */
    protected $im;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->composer = new Composer();

        $config = new Config();
        $this->composer->setConfig($config);

        /** @var RootPackageInterface|\PHPUnit_Framework_MockObject_MockObject $package */
        $package = $this->getMock('Composer\Package\RootPackageInterface');
        $package
            ->expects($this->any())
            ->method('getExtra')
            ->willReturn(array(
                SharedPackageInstaller::PACKAGE_TYPE => array(
                    'vendor-dir'  => sys_get_temp_dir() . '/composer-test-vendor-shared'
                )
            ))
        ;
        $this->composer->setPackage($package);

        $this->im = $this->getMock('Composer\Installer\InstallationManager');
        $this->composer->setInstallationManager($this->im);

        $this->io = $this->getMock('Composer\IO\IOInterface');
    }

    /**
     * @test
     */
    public function active()
    {
        $this->im->expects($this->once())
            ->method('addInstaller')
        ;

        $plugin = new SharedPackagePlugin();
        $plugin->activate($this->composer, $this->io);
    }
}


================================================
FILE: tests/unit/Test/Unit/LEtudiant/Composer/Util/SymlinkFilesystemTest.php
================================================
<?php

/*
 * This file is part of the "Composer Shared Package Plugin" package.
 *
 * https://github.com/Letudiant/composer-shared-package-plugin
 *
 * For the full license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Test\Unit\LEtudiant\Composer\Util;

use LEtudiant\Composer\Util\SymlinkFilesystem;

/**
 * @author Sylvain Lorinet <sylvain.lorinet@gmail.com>
 *
 * @covers \LEtudiant\Composer\Util\SymlinkFilesystem
 */
class SymlinkFilesystemTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var string
     */
    protected $testDir;


    /**
     * @inheritdoc
     */
    protected function setUp()
    {
        parent::setUp();

        $this->testDir = sys_get_temp_dir() . '/composer-filesystem-test';
        mkdir($this->testDir);
    }

    /**
     * @inheritdoc
     */
    protected function tearDown()
    {
        if (is_link($this->testDir . '/foo')) {
            unlink($this->testDir . '/foo');
        }

        if (is_dir($this->testDir)) {
            rmdir($this->testDir);
        }
    }

    /**
     * @test
     */
    public function ensureSymlinkExistsWhenExists()
    {
        symlink(sys_get_temp_dir(), $this->testDir . '/foo');

        $filesystem = new SymlinkFilesystem();
        $this->assertFalse($filesystem->ensureSymlinkExists(sys_get_temp_dir(), $this->testDir . '/foo'));
    }

    /**
     * @test
     */
    public function ensureSymlinkExistsWhenNotExists()
    {
        $filesystem = new SymlinkFilesystem();
        $this->assertTrue($filesystem->ensureSymlinkExists(sys_get_temp_dir(), $this->testDir . '/foo'));
        $this->assertFileExists($this->testDir . '/foo');
    }

    /**
     * @test
     */
    public function removeSymlinkWhenExists()
    {
        symlink(sys_get_temp_dir(), $this->testDir . '/foo');

        $filesystem = new SymlinkFilesystem();
        $this->assertTrue($filesystem->removeSymlink($this->testDir . '/foo'));
        $this->assertFileNotExists($this->testDir . '/foo');
    }

    /**
     * @test
     */
    public function removeSymlinkWhenNotExists()
    {
        $filesystem = new SymlinkFilesystem();
        $this->assertFalse($filesystem->removeSymlink($this->testDir . '/foo'));
    }

    /**
     * @test
     */
    public function removeEmptyDirectoryWhenExists()
    {
        $filesystem = new SymlinkFilesystem();
        $this->assertTrue($filesystem->removeEmptyDirectory($this->testDir));
        $this->assertFileNotExists($this->testDir);
    }

    /**
     * @test
     */
    public function removeEmptyDirectoryWhenNotEmpty()
    {
        symlink(sys_get_temp_dir(), $this->testDir . '/foo');

        $filesystem = new SymlinkFilesystem();
        $this->assertFalse($filesystem->removeEmptyDirectory($this->testDir));
        $this->assertFileExists($this->testDir);
    }

    /**
     * @test
     */
    public function removeEmptyDirectoryWhenNotExists()
    {
        $filesystem = new SymlinkFilesystem();
        $this->assertFalse($filesystem->removeEmptyDirectory($this->testDir . '/bar'));
    }
}
Download .txt
gitextract_uxe5wr2i/

├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── docs/
│   ├── all-available-configurations.md
│   └── how-to-use/
│       ├── disable-this-plugin-in-development-environment.md
│       ├── update-only-your-own-packages.md
│       └── work-with-satis.md
├── phpunit.xml.dist
├── src/
│   └── LEtudiant/
│       └── Composer/
│           ├── Data/
│           │   └── Package/
│           │       ├── PackageDataManagerInterface.php
│           │       └── SharedPackageDataManager.php
│           ├── Installer/
│           │   ├── Config/
│           │   │   └── SharedPackageInstallerConfig.php
│           │   ├── SharedPackageInstaller.php
│           │   └── Solver/
│           │       ├── SharedPackageInstallerSolver.php
│           │       └── SharedPackageSolver.php
│           ├── SharedPackagePlugin.php
│           └── Util/
│               └── SymlinkFilesystem.php
└── tests/
    ├── bootstrap.php
    └── unit/
        └── Test/
            └── Unit/
                └── LEtudiant/
                    └── Composer/
                        ├── Data/
                        │   └── Package/
                        │       └── SharedPackageDataManagerTest.php
                        ├── Installer/
                        │   ├── Config/
                        │   │   └── SharedPackageInstallerConfigTest.php
                        │   ├── SharedPackageInstallerTest.php
                        │   └── Solver/
                        │       ├── SharedPackageInstallerSolverNotSharedTest.php
                        │       ├── SharedPackageInstallerSolverSharedTest.php
                        │       └── SharedPackageSolverTest.php
                        ├── SharedPackagePluginTest.php
                        └── Util/
                            └── SymlinkFilesystemTest.php
Download .txt
SYMBOL INDEX (164 symbols across 16 files)

FILE: src/LEtudiant/Composer/Data/Package/PackageDataManagerInterface.php
  type PackageDataManagerInterface (line 20) | interface PackageDataManagerInterface
    method addPackageUsage (line 27) | public function addPackageUsage(PackageInterface $package);
    method removePackageUsage (line 34) | public function removePackageUsage(PackageInterface $package);
    method getPackageUsage (line 43) | public function getPackageUsage(PackageInterface $package);
    method setPackageInstallationSource (line 48) | public function setPackageInstallationSource(PackageInterface $package);
    method setVendorDir (line 55) | public function setVendorDir($vendorDir);

FILE: src/LEtudiant/Composer/Data/Package/SharedPackageDataManager.php
  class SharedPackageDataManager (line 20) | class SharedPackageDataManager implements PackageDataManagerInterface
    method __construct (line 43) | public function __construct(Composer $composer)
    method setVendorDir (line 51) | public function setVendorDir($vendorDir)
    method updatePackageUsageFile (line 60) | protected function updatePackageUsageFile(PackageInterface $package, a...
    method addPackageUsage (line 89) | public function addPackageUsage(PackageInterface $package)
    method removePackageUsage (line 106) | public function removePackageUsage(PackageInterface $package)
    method getPackageUsage (line 128) | public function getPackageUsage(PackageInterface $package)
    method initializePackageData (line 136) | protected function initializePackageData()
    method getPackageDataKey (line 153) | protected function getPackageDataKey(PackageInterface $package, $key, ...
    method setPackageInstallationSource (line 170) | public function setPackageInstallationSource(PackageInterface $package)

FILE: src/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfig.php
  class SharedPackageInstallerConfig (line 21) | class SharedPackageInstallerConfig
    method __construct (line 63) | public function __construct($originalRelativeVendorDir, $originalAbsol...
    method setSymlinkDirectory (line 87) | protected function setSymlinkDirectory($baseDir, array $extraConfigs)
    method setVendorDir (line 106) | protected function setVendorDir($baseDir, array $extraConfigs)
    method setSymlinkBasePath (line 126) | protected function setSymlinkBasePath(array $extraConfigs)
    method setIsSymlinkEnabled (line 155) | protected function setIsSymlinkEnabled(array $extraConfigs)
    method getPackageList (line 169) | public function getPackageList()
    method setPackageList (line 177) | public function setPackageList(array $extraConfigs)
    method isSymlinkEnabled (line 193) | public function isSymlinkEnabled()
    method getVendorDir (line 201) | public function getVendorDir()
    method getSymlinkDir (line 209) | public function getSymlinkDir()
    method getOriginalVendorDir (line 219) | public function getOriginalVendorDir($endingSlash = false)
    method getSymlinkBasePath (line 231) | public function getSymlinkBasePath()

FILE: src/LEtudiant/Composer/Installer/SharedPackageInstaller.php
  class SharedPackageInstaller (line 30) | class SharedPackageInstaller extends LibraryInstaller
    method __construct (line 58) | public function __construct(
    method getInstallPath (line 79) | public function getInstallPath(PackageInterface $package)
    method getPackageVendorSymlink (line 99) | protected function getPackageVendorSymlink(PackageInterface $package)
    method install (line 108) | public function install(InstalledRepositoryInterface $repo, PackageInt...
    method isInstalled (line 127) | public function isInstalled(InstalledRepositoryInterface $repo, Packag...
    method update (line 140) | public function update(InstalledRepositoryInterface $repo, PackageInte...
    method uninstall (line 165) | public function uninstall(InstalledRepositoryInterface $repo, PackageI...
    method isSourceDirUnused (line 193) | protected function isSourceDirUnused(PackageInterface $package)
    method createPackageVendorSymlink (line 203) | protected function createPackageVendorSymlink(PackageInterface $package)
    method getSymlinkSourcePath (line 223) | protected function getSymlinkSourcePath(PackageInterface $package)
    method removePackageVendorSymlink (line 245) | protected function removePackageVendorSymlink(PackageInterface $package)
    method supports (line 267) | public function supports($packageType)

FILE: src/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolver.php
  class SharedPackageInstallerSolver (line 26) | class SharedPackageInstallerSolver implements InstallerInterface
    method __construct (line 54) | public function __construct(
    method getInstallPath (line 72) | public function getInstallPath(PackageInterface $package)
    method install (line 85) | public function install(InstalledRepositoryInterface $repo, PackageInt...
    method isInstalled (line 100) | public function isInstalled(InstalledRepositoryInterface $repo, Packag...
    method update (line 112) | public function update(InstalledRepositoryInterface $repo, PackageInte...
    method uninstall (line 132) | public function uninstall(InstalledRepositoryInterface $repo, PackageI...
    method supports (line 150) | public function supports($packageType)

FILE: src/LEtudiant/Composer/Installer/Solver/SharedPackageSolver.php
  class SharedPackageSolver (line 21) | class SharedPackageSolver
    method __construct (line 37) | public function __construct(SharedPackageInstallerConfig $config)
    method isSharedPackage (line 57) | public function isSharedPackage(PackageInterface $package)
    method createCallbacks (line 86) | protected function createCallbacks(array $packageList)

FILE: src/LEtudiant/Composer/SharedPackagePlugin.php
  class SharedPackagePlugin (line 28) | class SharedPackagePlugin implements PluginInterface
    method activate (line 34) | public function activate(Composer $composer, IOInterface $io)
    method setConfig (line 56) | protected function setConfig(Composer $composer)

FILE: src/LEtudiant/Composer/Util/SymlinkFilesystem.php
  class SymlinkFilesystem (line 19) | class SymlinkFilesystem extends Filesystem
    method ensureSymlinkExists (line 29) | public function ensureSymlinkExists($sourcePath, $symlinkPath)
    method removeSymlink (line 47) | public function removeSymlink($symlinkPath)
    method removeEmptyDirectory (line 69) | public function removeEmptyDirectory($directoryPath)

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Data/Package/SharedPackageDataManagerTest.php
  class SharedPackageDataManagerTest (line 24) | class SharedPackageDataManagerTest extends \PHPUnit_Framework_TestCase
    method setUp (line 45) | protected function setUp()
    method tearDown (line 68) | protected function tearDown()
    method getPackageUsageWithoutFile (line 78) | public function getPackageUsageWithoutFile()
    method getPackageUsageWithFile (line 89) | public function getPackageUsageWithFile()
    method addPackageUsageWithoutData (line 103) | public function addPackageUsageWithoutData()
    method addPackageUsageWithHasData (line 150) | public function addPackageUsageWithHasData()
    method removePackageUsageWithoutData (line 201) | public function removePackageUsageWithoutData()
    method removePackageUsageWithData (line 242) | public function removePackageUsageWithData()
    method setPackageInstallationSourceWhenNotNull (line 311) | public function setPackageInstallationSourceWhenNotNull()
    method setPackageInstallationSourceWhenNull (line 328) | public function setPackageInstallationSourceWhenNull()
    method setPackageInstallationSourceWhenNullAndNoData (line 346) | public function setPackageInstallationSourceWhenNullAndNoData()
    method initializePackageData (line 363) | protected function initializePackageData()
    method clearPackageData (line 380) | protected function clearPackageData()
    method createPackage (line 391) | protected function createPackage()

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfigTest.php
  class SharedPackageInstallerConfigTest (line 22) | class SharedPackageInstallerConfigTest extends \PHPUnit_Framework_TestCase
    method tearDown (line 27) | protected function tearDown()
    method noVendorDirConfigured (line 41) | public function noVendorDirConfigured()
    method getVendorDir (line 53) | public function getVendorDir($vendorDirPath)
    method getVendorDirFromEnvVar (line 68) | public function getVendorDirFromEnvVar($vendorDirPath, $envVar)
    method getVendorDirFromEnvVarDataProvider (line 80) | public function getVendorDirFromEnvVarDataProvider()
    method getVendorDirDataProvider (line 97) | public function getVendorDirDataProvider()
    method getSymlinkDir (line 116) | public function getSymlinkDir($symlinkDirPath)
    method getSymlinkDirDataProvider (line 127) | public function getSymlinkDirDataProvider()
    method getSymlinkDirWithEmptyConfiguration (line 142) | public function getSymlinkDirWithEmptyConfiguration()
    method getOriginalVendorDir (line 154) | public function getOriginalVendorDir($expectedValue, $nullable)
    method getOriginalVendorDirDataProvider (line 164) | public function getOriginalVendorDirDataProvider()
    method getSymlinkBasePathWhenNull (line 181) | public function getSymlinkBasePathWhenNull()
    method getSymlinkBasePathWhenNotNull (line 191) | public function getSymlinkBasePathWhenNotNull()
    method getSymlinkBasePathWhenNotNullAndEndingSlash (line 202) | public function getSymlinkBasePathWhenNotNullAndEndingSlash()
    method getSymlinkBasePathWhenVendorDirIsRelative (line 213) | public function getSymlinkBasePathWhenVendorDirIsRelative()
    method getSymlinkBasePathWhenRelative (line 223) | public function getSymlinkBasePathWhenRelative()
    method getSymlinkBasePathFromEnvVar (line 234) | public function getSymlinkBasePathFromEnvVar()
    method isSymlinkEnabledDefaultValue (line 247) | public function isSymlinkEnabledDefaultValue()
    method setIsSymlinkEnabled (line 257) | public function setIsSymlinkEnabled()
    method setIsSymlinkEnabledWithString (line 271) | public function setIsSymlinkEnabledWithString()
    method setPackageListWrongTypeException (line 285) | public function setPackageListWrongTypeException()
    method setPackageList (line 296) | public function setPackageList()
    method setPackageListEmpty (line 309) | public function setPackageListEmpty()
    method createInstallerConfig (line 323) | protected function createInstallerConfig(array $extra, $relativeDir = ...

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/SharedPackageInstallerTest.php
  class SharedPackageInstallerTest (line 35) | class SharedPackageInstallerTest extends TestCase
    method setUp (line 101) | protected function setUp()
    method tearDown (line 170) | protected function tearDown()
    method testInstallerCreationShouldNotCreateVendorDirectory (line 183) | public function testInstallerCreationShouldNotCreateVendorDirectory()
    method testInstallerCreationShouldNotCreateBinDirectory (line 193) | public function testInstallerCreationShouldNotCreateBinDirectory()
    method isInstalled (line 203) | public function isInstalled()
    method install (line 228) | public function install()
    method installWithSymlinkBasePath (line 277) | public function installWithSymlinkBasePath()
    method installWithSymlinkBasePathAndTargetDir (line 299) | public function installWithSymlinkBasePathAndTargetDir()
    method installWithSymlinkDisabled (line 326) | public function installWithSymlinkDisabled()
    method updateCode (line 343) | public function updateCode()
    method updateFull (line 387) | public function updateFull()
    method uninstall (line 411) | public function uninstall()
    method uninstallKeepSources (line 461) | public function uninstallKeepSources()
    method getInstallPath (line 503) | public function getInstallPath()
    method getInstallPathWithTargetDir (line 517) | public function getInstallPathWithTargetDir()
    method supports (line 539) | public function supports()
    method createConfigMock (line 555) | protected function createConfigMock($vendorDir = null, $symlinkDir = n...
    method createPackageMock (line 616) | protected function createPackageMock($prettyName = 'letudiant/foo-bar')
    method createInstaller (line 663) | protected function createInstaller($config = null, $filesystem = null)

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverNotSharedTest.php
  class SharedPackageInstallerSolverNotSharedTest (line 27) | class SharedPackageInstallerSolverNotSharedTest extends \PHPUnit_Framewo...
    method setUp (line 43) | protected function setUp()
    method getInstallPath (line 58) | public function getInstallPath()
    method install (line 74) | public function install()
    method isInstalled (line 90) | public function isInstalled()
    method update (line 106) | public function update()
    method uninstall (line 123) | public function uninstall()
    method supports (line 139) | public function supports()
    method createSolver (line 147) | protected function createSolver()
    method createPackageMock (line 167) | protected function createPackageMock()

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverSharedTest.php
  class SharedPackageInstallerSolverSharedTest (line 28) | class SharedPackageInstallerSolverSharedTest extends SharedPackageInstal...
    method setUp (line 44) | protected function setUp()
    method createSolver (line 59) | protected function createSolver()
    method updateWhenInitialNotInstalledException (line 82) | public function updateWhenInitialNotInstalledException()
    method uninstall (line 99) | public function uninstall()
    method update (line 122) | public function update()
    method uninstallWhenPackageNotInstalledException (line 149) | public function uninstallWhenPackageNotInstalledException()
    method supports (line 165) | public function supports()
    method createPackageMock (line 173) | protected function createPackageMock()

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageSolverTest.php
  class SharedPackageSolverTest (line 24) | class SharedPackageSolverTest extends \PHPUnit_Framework_TestCase
    method setUp (line 35) | protected function setUp()
    method constructAllShared (line 48) | public function constructAllShared()
    method constructWithPackageList (line 67) | public function constructWithPackageList()
    method constructWhenNull (line 92) | public function constructWhenNull()
    method createCallbacks (line 114) | public function createCallbacks($i, $expectedValue, $packagePrettyName)
    method createCallbacksDataProvider (line 134) | public function createCallbacksDataProvider()
    method isSharedPackageWithAllShared (line 162) | public function isSharedPackageWithAllShared()
    method isSharedPackageWithPackageList (line 187) | public function isSharedPackageWithPackageList()
    method createPackageMock (line 219) | public function createPackageMock($prettyName = null, $type = null)
    method createSolver (line 245) | protected function createSolver()

FILE: tests/unit/Test/Unit/LEtudiant/Composer/SharedPackagePluginTest.php
  class SharedPackagePluginTest (line 27) | class SharedPackagePluginTest extends \PHPUnit_Framework_TestCase
    method setUp (line 48) | protected function setUp()
    method active (line 79) | public function active()

FILE: tests/unit/Test/Unit/LEtudiant/Composer/Util/SymlinkFilesystemTest.php
  class SymlinkFilesystemTest (line 21) | class SymlinkFilesystemTest extends \PHPUnit_Framework_TestCase
    method setUp (line 32) | protected function setUp()
    method tearDown (line 43) | protected function tearDown()
    method ensureSymlinkExistsWhenExists (line 57) | public function ensureSymlinkExistsWhenExists()
    method ensureSymlinkExistsWhenNotExists (line 68) | public function ensureSymlinkExistsWhenNotExists()
    method removeSymlinkWhenExists (line 78) | public function removeSymlinkWhenExists()
    method removeSymlinkWhenNotExists (line 90) | public function removeSymlinkWhenNotExists()
    method removeEmptyDirectoryWhenExists (line 99) | public function removeEmptyDirectoryWhenExists()
    method removeEmptyDirectoryWhenNotEmpty (line 109) | public function removeEmptyDirectoryWhenNotEmpty()
    method removeEmptyDirectoryWhenNotExists (line 121) | public function removeEmptyDirectoryWhenNotExists()
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (130K chars).
[
  {
    "path": ".gitignore",
    "chars": 64,
    "preview": ".idea\ntests/build\nvendor\ncomposer.lock\ncomposer.phar\nphpunit.xml"
  },
  {
    "path": ".travis.yml",
    "chars": 779,
    "preview": "language: php\r\n\r\nphp:\r\n    - 5.5\r\n    - 5.6\r\n    - 7.0\r\n\r\nbefore_script:\r\n    - curl -s http://getcomposer.org/installer"
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 L'Etudiant\n\nPermission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "README.md",
    "chars": 10467,
    "preview": "# Composer - Shared Package Plugin\n\n[![Code Climate](https://codeclimate.com/github/Letudiant/composer-shared-package-pl"
  },
  {
    "path": "composer.json",
    "chars": 1099,
    "preview": "{\n    \"name\": \"letudiant/composer-shared-package-plugin\",\n    \"description\": \"This composer plugin allows you to share s"
  },
  {
    "path": "docs/all-available-configurations.md",
    "chars": 2043,
    "preview": "# Composer - Shared Package Plugin\n\n## All available configurations\n\nAll these configuration should set in the `extra` `"
  },
  {
    "path": "docs/how-to-use/disable-this-plugin-in-development-environment.md",
    "chars": 516,
    "preview": "# Composer - Shared Package Plugin\n\n## How to use\n\n### Disable this plugin in development environment (for CI purpose, f"
  },
  {
    "path": "docs/how-to-use/update-only-your-own-packages.md",
    "chars": 977,
    "preview": "# Composer - Shared Package Plugin\n\n## How to use\n\n### Update only your own packages\n\nYou can easily update your own pac"
  },
  {
    "path": "docs/how-to-use/work-with-satis.md",
    "chars": 915,
    "preview": "# Composer - Shared Package Plugin\n\n## How to use\n\n### Work with Satis : increase Composer speed\n\nAs we know, Composer i"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1141,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\r\n<phpunit backupGlobals=\"false\"\r\n         backupStaticAttributes=\"false\"\r\n     "
  },
  {
    "path": "src/LEtudiant/Composer/Data/Package/PackageDataManagerInterface.php",
    "chars": 1419,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "src/LEtudiant/Composer/Data/Package/SharedPackageDataManager.php",
    "chars": 4990,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "src/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfig.php",
    "chars": 7130,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "src/LEtudiant/Composer/Installer/SharedPackageInstaller.php",
    "chars": 8672,
    "preview": "<?php\n\n/*\n * This file is part of the \"Composer Shared Package Plugin\" package.\n *\n * https://github.com/Letudiant/compo"
  },
  {
    "path": "src/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolver.php",
    "chars": 4466,
    "preview": "<?php\n\n/*\n * This file is part of the \"Composer Shared Package Plugin\" package.\n *\n * https://github.com/Letudiant/compo"
  },
  {
    "path": "src/LEtudiant/Composer/Installer/Solver/SharedPackageSolver.php",
    "chars": 2981,
    "preview": "<?php\n\n/*\n * This file is part of the \"Composer Shared Package Plugin\" package.\n *\n * https://github.com/Letudiant/compo"
  },
  {
    "path": "src/LEtudiant/Composer/SharedPackagePlugin.php",
    "chars": 1935,
    "preview": "<?php\n\n/*\n * This file is part of the \"Composer Shared Package Plugin\" package.\n *\n * https://github.com/Letudiant/compo"
  },
  {
    "path": "src/LEtudiant/Composer/Util/SymlinkFilesystem.php",
    "chars": 2037,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 134,
    "preview": "<?php\r\n\r\nrequire __DIR__ . '/../vendor/autoload.php';\r\nrequire __DIR__ . '/../vendor/composer/composer/tests/Composer/Te"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Data/Package/SharedPackageDataManagerTest.php",
    "chars": 12825,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Installer/Config/SharedPackageInstallerConfigTest.php",
    "chars": 10098,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Installer/SharedPackageInstallerTest.php",
    "chars": 20847,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverNotSharedTest.php",
    "chars": 4662,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageInstallerSolverSharedTest.php",
    "chars": 6239,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Installer/Solver/SharedPackageSolverTest.php",
    "chars": 7342,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/SharedPackagePluginTest.php",
    "chars": 2335,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  },
  {
    "path": "tests/unit/Test/Unit/LEtudiant/Composer/Util/SymlinkFilesystemTest.php",
    "chars": 3232,
    "preview": "<?php\r\n\r\n/*\r\n * This file is part of the \"Composer Shared Package Plugin\" package.\r\n *\r\n * https://github.com/Letudiant/"
  }
]

About this extraction

This page contains the full source code of the Letudiant/composer-shared-package-plugin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (117.6 KB), approximately 28.0k tokens, and a symbol index with 164 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!