pingpong-labs / modules

Laravel 5 Modules
https://pingpong-labs.github.io/docs/modules.html
BSD 3-Clause "New" or "Revised" License
577 stars 151 forks source link

How to proper include and use a composer dependency in a module? #193

Closed marcotisi closed 8 years ago

marcotisi commented 9 years ago

Hi @gravitano,

I'm using your plugin to create a modulazired application. However, I can't figure out how to use an external library, like intervention/image, in a module. Here are the steps I took:

Create a new application and install pingpong modules

$ laravel new testcore

Crafting application...
> php -r "copy('.env.example', '.env');"
> php artisan clear-compiled
> php artisan optimize
Generating optimized class loader
> php artisan key:generate
Application key [3J6WvdEcA2IpTtuo6jV87XGUbNO8xJ39] set successfully.
Application ready! Build something amazing.

$ cd testcore
$ composer require pingpong/modules

Using version ^2.1 for pingpong/modules
./composer.json has been updated
> php artisan clear-compiled
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing laravelcollective/html (v5.1.3)
    Loading from cache

  - Installing erusev/parsedown (1.5.3)
    Loading from cache

  - Installing erusev/parsedown-extra (0.7.0)
    Loading from cache

  - Installing pingpong/support (v2.1.4)
    Loading from cache

  - Installing doctrine/lexer (v1.0.1)
    Loading from cache

  - Installing doctrine/annotations (v1.2.6)
    Loading from cache

  - Installing doctrine/collections (v1.3.0)
    Loading from cache

  - Installing doctrine/cache (v1.4.1)
    Loading from cache

  - Installing doctrine/common (v2.5.0)
    Loading from cache

  - Installing doctrine/dbal (v2.5.1)
    Loading from cache

  - Installing pingpong/generators (v2.1.4)
    Loading from cache

  - Installing pingpong/modules (v2.1.4)
    Loading from cache

Writing lock file
Generating autoload files
> php artisan optimize
Generating optimized class loader

Add Modules Service Provider and Alias to config/app.php

    'providers' => [
        Pingpong\Modules\ModulesServiceProvider::class,
        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
        ...
    ],
    'aliases' => [
        'Module'    => Pingpong\Modules\Facades\Module::class,
        'App'       => Illuminate\Support\Facades\App::class,
        ...
    ],

Add PSR-4 loading of Modules namespace

{
  "autoload": {
    "psr-4": {
      "App\\": "app/",
      "Modules\\": "modules/"
    }
  }
}
$ composer dump-autoload

Publish vendor files

$ php artisan vendor:publish

Copied File [/vendor/pingpong/modules/src/config/config.php] To [/config/modules.php]
Publishing complete for tag []!

$ php artisan module:setup
Modules directory created successfully
Assets directory created successfully

Create a new module

$ php artisan module:make Media
Created : /var/www/testcore/modules/Media/start.php
Created : /var/www/testcore/modules/Media/Http/routes.php
Created : /var/www/testcore/modules/Media/module.json
Created : /var/www/testcore/modules/Media/Resources/views/index.blade.php
Created : /var/www/testcore/modules/Media/Resources/views/layouts/master.blade.php
Created : /var/www/testcore/modules/Media/Config/config.php
Created : /var/www/testcore/modules/Media/composer.json
Created : /var/www/testcore/modules/Media/Database/Seeders/MediaDatabaseSeeder.php
Created : /var/www/testcore/modules/Media/Providers/MediaServiceProvider.php
Created : /var/www/testcore/modules/Media/Http/Controllers/MediaController.php
Module [Media] created successfully.

Everything until this point works fine: i can browse to testcore.localhost/media and Laravel successfully shows me the module Welcome page. Even the module status shows me that everything is ok:

php artisan module:list
+-------+---------+-------+---------------------------------+
| Name  | Status  | Order | Path                            |
+-------+---------+-------+---------------------------------+
| Media | Enabled | 0     | /var/www/testcore/modules/Media |
+-------+---------+-------+---------------------------------+

Add composer dependency

$ cd modules/Media
$ composer require intervention/image
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing psr/http-message (1.0)
    Loading from cache

  - Installing guzzlehttp/psr7 (1.1.0)
    Loading from cache

  - Installing intervention/image (2.3.1)
    Loading from cache

intervention/image suggests installing intervention/imagecache (Caching extension for the Intervention Image library)
Writing lock file
Generating autoload files

Everything is fine again:

php artisan module:list
+-------+---------+-------+---------------------------------+
| Name  | Status  | Order | Path                            |
+-------+---------+-------+---------------------------------+
| Media | Enabled | 0     | /var/www/testcore/modules/Media |
+-------+---------+-------+---------------------------------+

But now i need to include the Intervention Service Provider to be able to use it, so i edit my module.json file like this:

{
    "name": "Media",
    "alias": "media",
    "description": "",
    "keywords": [],
    "active": 1,
    "order": 0,
    "providers": [
        "Modules\\Media\\Providers\\MediaServiceProvider",
        "Intervention\\Image\\ImageServiceProvider"
    ],
    "aliases":{},
    "files": [
        "start.php"
    ]
}

If I check again the modules statuses, artisan give me an error:

$ php artisan module:list
PHP Fatal error:  Class 'Intervention\Image\ImageServiceProvider' not found in /var/www/testcore/vendor/laravel/framework/src/Illuminate/Foundation/Application.php on line 575

  [Symfony\Component\Debug\Exception\FatalErrorException]    
  Class 'Intervention\Image\ImageServiceProvider' not found  

I think that this is related to not including composer's autoload.php inside modules/Media/vendor/.

Are there any correct ways to do this? I spotted a class named Starter inside pingpong/modules which seems to do exactly this, but I don't know how to use it.

Please help me!

marcotisi commented 9 years ago

@nWidart you do this in every AsgardCMS's module. How can you do that?

nWidart commented 9 years ago

I use composer installers, but looks pretty much the same yes. One problem though, composer installers, will only check the module's composer.json on first install. If you add a dependency later on, it won't get picked up by composer update.

marcotisi commented 9 years ago

@nWidart thank you for your response! I can't rely on composer-installers because our project will not be open-sourced... Will wait for an answer from @gravitano ...

zek commented 9 years ago

:+1:

IfnotFr commented 9 years ago

Same here. I tried to add theses lines on the module composer.json :

"config": {
   "vendor-dir": "../../vendor"
},

It works well, but remove all dependencies of the laravel project from the main composer.json :D :santa:

EDIT : This composer/composer#183 is interesting for us, but opened since 2012 so ... i think there will no suport in days.

EDIT 2 : One dirty way with no support of concurrency version and duplicate dependancies ... but works for small projects with separate requires.

1 : Update the main composer.json and add a automatic composer update for all modules after the install / update command (into post-install-cmd and post-update-cmd) :

for D in modules/*; do [ -d \"${D}\" ] && cd $D; composer update; cd ../..; done"

2 : Patch bootstrap/autoload.php with theses lines under

require __DIR__.'/../vendor/autoload.php';

foreach(glob(__DIR__ . '/../modules/*') as $folder) {
    $autoload = $folder . '/vendor/autoload.php';
    if(file_exists($autoload)) {
        require $autoload;
    }
}
noxify commented 9 years ago

Hi,

Did you try this: https://github.com/wikimedia/composer-merge-plugin

And what if you're integrate your module as a composer package? This should also solve your problems :)

IfnotFr commented 9 years ago

This is a private project, so ... i dont want to include a composer workflow into this one. It is just a way for me to make a clean architecture of a project.

I think we do not have to make module as composer packages with pingpong-labs/modules for using module composer dependancies. This should be lightweight as possible :)

I will try your solution, if it works i will explain my process here. Thanks !

nWidart commented 9 years ago

You can use Satis or Toran proxy to handle private packages. ( https://getcomposer.org/doc/05-repositories.md#hosting-your-own )

Also, with the latest composer version you can have local packages. ( https://getcomposer.org/doc/05-repositories.md#path)

IfnotFr commented 9 years ago

Thanks for the link, i think there are many ways to handle this with all suggestions :)

@nWidart : I confirm that i doesnt want a composer package for each of my modules, it is just a architecture of an app, i cannot use a git per module etc ...

solution of @noxify works well ! :) Just add the package, set to merge the files from modules/*/composer.json and it works :)

noxify commented 9 years ago

perfect @Ifnot - good to know that it's working :)

Then we can close this, or?

IfnotFr commented 9 years ago

I think we can close this as implementation of https://github.com/wikimedia/composer-merge-plugin solves the problem.

SIMPLE HOWTO :

First, install the package :

composer require wikimedia/composer-merge-plugin

Next, require modules composer.json into your main project composer.json :

"extra": {
    "merge-plugin": {
        "include": [
            "modules/*/composer.json"
        ]
    }
}

And then, simply add dependancies into your modules composer.json as usual

For Class 'xxx\xxx\xxxServiceProvider' not found :

When requiring dependencies you may have this error. This is because we require a service provider not already loaded by composer.

The composer update throw this error because pingpong-labs/modules is ready and try to register modules service providers. But wikimedia/composer-merge-plugin did not required modules packages yet ...

Solution : do a composer update --no-scripts for ignore the pre-update-cmd php artisan clear-compiled from the main composer.json witch throw the error.

PS : Maybe this howto can be usefull for more people ? Maybe it will be interesting to add it into documentation ?

spescina commented 8 years ago

No luck with this setup. Anyone confirming that this approach should work?

nWidart commented 8 years ago

What are you trying to do ?

To include a dependency:

It has to be both for the latter because composer-installers doesn't check those composer.json files. It only does so when installing the project.

spescina commented 8 years ago

I'm planning to build reusable modules. It's a bit tricky what you're suggesting but should work, let me try. I've also tried the merge plugin but couldn't get it to work...

Tnx

nWidart commented 8 years ago

It's not really tricky, you only need to add your dependency in 2 composer.json files, instead of one. That's it. 😄

spescina commented 8 years ago

Yeah but it's not an ideal workflow in my opinion... there's some caveats in it

nWidart commented 8 years ago

That's how composer works. I haven't had any issues with it, both for testing the modules & re-using them.

spescina commented 8 years ago

How are you dealing with modules global require in the main project and vcs? Do you leave them in it until modules are completed?

nWidart commented 8 years ago

You could either use the vcs composer type indeed, or via a submodule.

I would work on the module until it's done. Copy the module outside, init a git repository in it, push on a remote, add to packagist/satis/toranproxy, and require that module back into the project using vcs type so it can still be edited.

redtusker commented 1 week ago

How can i Vendor:publish in a module (having issue with modules)