Repository: ansible/ansible

Issue:
Composer Module without `working_dir` and with `global`
##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
- Composer

##### ANSIBLE VERSION
```
ansible 2.2.1.0
  config file = 
  configured module search path = Default w/o overrides
```

##### OS / ENVIRONMENT
N/A

##### SUMMARY
Composer can install packages [globally](https://getcomposer.org/doc/03-cli.md#global) with `composer global require ...`. It automatically changes to the users local composer directory so no working directory parameter is required.

##### STEPS TO REPRODUCE
Use the `composer` module and try to use the `global require` command without `working_dir`.

```yaml
---

- hosts: all
  tasks:
  - composer:
      command: global require
      arguments: fabpot/php-cs-fixer
```

##### EXPECTED RESULTS
I expected the package to be installed globally.

##### ACTUAL RESULTS
The `composer` module does not work at all with this command and/or without working directory

```
fatal: [default]: FAILED! => {"changed": false, "failed": true, "msg": "Use the 'arguments' param for passing arguments with the 'command'"}
```



Discussion:
cc @dmtrs @resmo
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
+1, I have the same trouble. My temporary solution:
```
- name: install composer asset plugin
  shell: 'composer global require "fxp/composer-asset-plugin:^1.3.1"'
```


Resolving patch:
diff --git a/lib/ansible/modules/packaging/language/composer.py b/lib/ansible/modules/packaging/language/composer.py
--- a/lib/ansible/modules/packaging/language/composer.py
+++ b/lib/ansible/modules/packaging/language/composer.py
@@ -52,10 +52,20 @@
         default: null
     working_dir:
         description:
-            - Directory of your project (see --working-dir).
-        required: true
+            - Directory of your project (see --working-dir). This is required when
+              the command is not run globally.
+            - Will be ignored if C(global_command=true).
+        required: false
         default: null
         aliases: [ "working-dir" ]
+    global_command:
+        version_added: "2.4"
+        description:
+            - Runs the specified command globally.
+        required: false
+        choices: [ true, false]
+        default: false
+        aliases: [ "global-command" ]
     prefer_source:
         description:
             - Forces installation from package sources when possible (see --prefer-source).
@@ -133,6 +143,12 @@
     arguments: package/package /path/to/project ~1.0
     working_dir: /path/to/project
     prefer_dist: yes
+
+# Installs package globally
+- composer:
+    command: require
+    global_command: yes
+    arguments: my/package
 '''
 
 import re
@@ -158,10 +174,12 @@ def get_available_options(module, command='install'):
     return command_help_json['definition']['options']
 
 
-def composer_command(module, command, arguments="", options=[]):
+def composer_command(module, command, arguments="", options=None, global_command=False):
+    if options is None:
+        options = []
     php_path = module.get_bin_path("php", True, ["/usr/local/bin"])
     composer_path = module.get_bin_path("composer", True, ["/usr/local/bin"])
-    cmd = "%s %s %s %s %s" % (php_path, composer_path, command, " ".join(options), arguments)
+    cmd = "%s %s %s %s %s %s" % (php_path, composer_path, "global" if global_command else "", command, " ".join(options), arguments)
     return module.run_command(cmd)
 
 
@@ -170,7 +188,8 @@ def main():
         argument_spec=dict(
             command=dict(default="install", type="str", required=False),
             arguments=dict(default="", type="str", required=False),
-            working_dir=dict(type="path", aliases=["working-dir"], required=True),
+            working_dir=dict(type="path", aliases=["working-dir"]),
+            global_command=dict(default=False, type="bool", aliases=["global-command"]),
             prefer_source=dict(default=False, type="bool", aliases=["prefer-source"]),
             prefer_dist=dict(default=False, type="bool", aliases=["prefer-dist"]),
             no_dev=dict(default=True, type="bool", aliases=["no-dev"]),
@@ -179,6 +198,7 @@ def main():
             optimize_autoloader=dict(default=True, type="bool", aliases=["optimize-autoloader"]),
             ignore_platform_reqs=dict(default=False, type="bool", aliases=["ignore-platform-reqs"]),
         ),
+        required_if=[('global_command', False, ['working_dir'])],
         supports_check_mode=True
     )
 
@@ -188,6 +208,7 @@ def main():
         module.fail_json(msg="Use the 'arguments' param for passing arguments with the 'command'")
 
     arguments = module.params['arguments']
+    global_command = module.params['global_command']
     available_options = get_available_options(module=module, command=command)
 
     options = []
@@ -204,7 +225,8 @@ def main():
             option = "--%s" % option
             options.append(option)
 
-    options.extend(['--working-dir', "'%s'" % module.params['working_dir']])
+    if not global_command:
+        options.extend(['--working-dir', "'%s'" % module.params['working_dir']])
 
     option_params = {
         'prefer_source': 'prefer-source',
@@ -224,7 +246,7 @@ def main():
     if module.check_mode:
         options.append('--dry-run')
 
-    rc, out, err = composer_command(module, command, arguments, options)
+    rc, out, err = composer_command(module, command, arguments, options, global_command)
 
     if rc != 0:
         output = parse_out(err)


Test patch: