Thanks to visit codestin.com
Credit goes to github.com

Skip to content

calling this method twice do not make sense #19909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

christian-weiss
Copy link

small refactoring

@javiereguiluz
Copy link
Member

I agree that the code looks weird and it doesn't make sense ... but if you remove that method, 1 test fails:

1) Symfony\Component\Console\Tests\ApplicationTest::testRun
->run() runs the help command if --help is passed
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
 'Usage:
-  list [options] [--] [<namespace>]
+  list [options] [--] <command> [<namespace>]

@linaori
Copy link
Contributor

linaori commented Sep 12, 2016

Looks like a dx issue to me:

/**
 * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
 */
public function getSynopsis($short = false)
{
    $key = $short ? 'short' : 'long';

    if (!isset($this->synopsis[$key])) {
        $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
    }

    return $this->synopsis[$key];
}

Could become getLongSynopsis and getShortSynopsis

@christian-weiss
Copy link
Author

javiereguiluz your are right. Sorry that i did not dig deeper into it.

getSynopsis() avoids the single responsibility principle (SRP). Because:

  • it is a getter method AND
  • it initializes an internal array

Calling a getter method without using its return value is a:

  • SRP violation indicator OR
  • just waist of resources

Instead of calling:
$this->getSynopsis(true);
$this->getSynopsis(false);

we should call:
$this->initLongAndShortSynopsis() (needs to be implemented)
or
$this->initLongSynopsis() (needs to be implemented)
$this->initShortSynopsis() (needs to be implemented)

getSynopsis() should be refactored to use the init methods.

@nicolas-grekas
Copy link
Member

Status: needs work

@ro0NL
Copy link
Contributor

ro0NL commented Sep 17, 2016

@christian-weiss have you digged in why calling this method twice does not make sense exactly? The comment is more or less clear about it

// force the creation of the synopsis before the merge with the app definition

Ie. what are you trying to solve? getSynopsis follows SRP perfectly fine, it just lazy loads its value. Agree the long/short version could be separated in 2 methods.. however i dont think that's worth the effort / a real (SRP) issue.

@robfrawley
Copy link
Contributor

@ro0NL

My understanding is @christian-weiss is trying to improve DX, and obviously calling a getter method twice and ignoring its return value isn't particularly straight-forward code to read.

In the context of this specific piece of code, I don't have a strong opinion as to whether it is beneficial for this method to remain exactly as is, or for it to be refactored into initializeSynopsis, getShortSynopsis, and getLongSynopsis (or similar).

I simply want to point out that the SRP deficiency @christian-weiss asserted is absolutely valid. Saying (and I quote) "getSynopsis follows SRP perfectly fine" couldn't be farther from the truth: the getSynopsis method obviously has three distinct responsibilities:

  • Class member initialization.
  • Getter method (for long synopsis).
  • Getter method (for short synopsis).

Now, to be clear, this isn't meant to be an endorsement of refactoring the code: sometimes the most functional code does not perfectly model every rule, and that's just fine.

But then, the conversation should not be whether or not the method follows the single responsibility principle (it doesn't), but instead focus on where to draw the line between effective SRP (and inherit DX clarity that is achieved through the application of this rule) and inefficient SRP (and the excessive verbosity that results).

@ro0NL
Copy link
Contributor

ro0NL commented Sep 18, 2016

Imo the problem is pre-initializing here because otherwise mergeApplicationDefinition wont work?

Im not fully aware of inner workings here... but just removing a method call for cosmetic reasons dont seem right.

Hence i asked have you digged in why calling this method twice does not make sense exactly?

"getSynopsis follows SRP perfectly fine"

Yes it's a somewhat pragmatic method (2in1 getter), but valid SRP imo. It's only responsibility is to return a short/ or long synopsis.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 18, 2016

Ok. Initializing is needed because otherwise mergeApplicationDefinition will update the definition and we cant get the original/needed synopsis anymore (getSynopsis couples with $this->definition).

However it looks inconsistent when using descriptors, TextDescriptor does this as well, but Xml only inits the long version. This could be intented, but im not sure.

At first sight i'd say move the getSynopsis(true) and getSynopsis(false) calls to mergeApplicationDefinition and cleanup. For better DX we can consider initSynopsis and use it in getSynopsis as well.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 18, 2016

Passes tests for the console component.

diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php
index 0d5001b..2df1ec1 100644
--- a/src/Symfony/Component/Console/Command/Command.php
+++ b/src/Symfony/Component/Console/Command/Command.php
@@ -209,10 +209,6 @@ class Command
      */
     public function run(InputInterface $input, OutputInterface $output)
     {
-        // force the creation of the synopsis before the merge with the app definition
-        $this->getSynopsis(true);
-        $this->getSynopsis(false);
-
         // add the application arguments and options
         $this->mergeApplicationDefinition();

@@ -300,6 +296,10 @@ class Command
             return;
         }

+        // force the creation of the synopsis before the merge with the app definition
+        $this->getSynopsis(true);
+        $this->getSynopsis(false);
+
         $this->definition->addOptions($this->application->getDefinition()->getOptions());

         if ($mergeArgs) {
diff --git a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php
index 87e38fd..c3852a8 100644
--- a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php
+++ b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php
@@ -152,7 +152,6 @@ class JsonDescriptor extends Descriptor
      */
     private function getCommandData(Command $command)
     {
-        $command->getSynopsis();
         $command->mergeApplicationDefinition(false);

         return array(
diff --git a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php
index 2eb9944..3af4a15 100644
--- a/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php
+++ b/src/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php
@@ -89,7 +89,6 @@ class MarkdownDescriptor extends Descriptor
      */
     protected function describeCommand(Command $command, array $options = array())
     {
-        $command->getSynopsis();
         $command->mergeApplicationDefinition(false);

         $this->write(
diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
index 719f238..cc12aac 100644
--- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
+++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
@@ -134,8 +134,6 @@ class TextDescriptor extends Descriptor
      */
     protected function describeCommand(Command $command, array $options = array())
     {
-        $command->getSynopsis(true);
-        $command->getSynopsis(false);
         $command->mergeApplicationDefinition(false);

         $this->writeText('<comment>Usage:</comment>', $options);
diff --git a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php
index b5676be..69e5a25 100644
--- a/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php
+++ b/src/Symfony/Component/Console/Descriptor/XmlDescriptor.php
@@ -59,7 +59,6 @@ class XmlDescriptor extends Descriptor
         $dom = new \DOMDocument('1.0', 'UTF-8');
         $dom->appendChild($commandXML = $dom->createElement('command'));

-        $command->getSynopsis();
         $command->mergeApplicationDefinition(false);

         $commandXML->setAttribute('id', $command->getName());

@linaori
Copy link
Contributor

linaori commented Sep 18, 2016

It still looks very weird to call getters and not doing anything with them. Right now it's just getters with side effects it seems

@ro0NL
Copy link
Contributor

ro0NL commented Sep 18, 2016

Agree, but at least it looks weird in 1 place only :)

@christian-weiss
Copy link
Author

If you have trouble with my SRP definition, just leave that argument as it is.

Premature optimization is the root of all evil (Donald Knuth).
There is no lazy loading. There is lazy initializing. Lazy initializing do not make sense for scalar values. It adds complexity while not inproving performance or memory footprint at a way that it payback a benefit.

The side effect of a getter is worse and avoids POLS / POSA.
https://en.wikipedia.org/wiki/Principle_of_least_astonishment.

To remove the DX issue just move the init stuff forward to a init method or to the constructor. Or give a valid reason to delay the initialization.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 20, 2016

Agree this all is not designed perfectly and perhaps violates one or more patterns in one or more ways. But lets be pragmatic.. the intention of the code is clear by now?

Imo. the main problem is mergeApplicationDefinition violating SRP for being depended on calling getSynopsis first. See my diff that fixes that.. we can argue if this getSynopsis call really needs to be renamed to initSynopsises/whatever whereas the result will be the same.

edit: im not sure what the effect will be on

$command->mergeApplicationDefinition();
ie. maybe the current version is intended after all...

@stof
Copy link
Member

stof commented Sep 21, 2016

There is no lazy loading. There is lazy initializing. Lazy initializing do not make sense for scalar values. It adds complexity while not inproving performance or memory footprint at a way that it payback a benefit.

The goal is not to optimize memory, but to avoid computing the synopsis for all cases which don't need it (it is needed only when displaying the help, not when actually running the command). $this->definition->getSynopsis($short) involves quite some logic.

@ro0NL
Copy link
Contributor

ro0NL commented Sep 21, 2016

@stof would mergeApplicationDefinitionWithSynopsis make things more clear perhaps?

@fabpot
Copy link
Member

fabpot commented Oct 5, 2016

Closing in favor of #20054

@fabpot fabpot closed this Oct 5, 2016
fabpot added a commit that referenced this pull request Aug 13, 2020
…finition (ro0NL)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Console] Different approach on merging application definition

| Q | A |
| --- | --- |
| Branch? | "master" |
| Bug fix? | yes |
| New feature? | not really (refactoring) |
| BC breaks? | no |
| Deprecations? | no |
| Tests pass? | yes |
| Fixed tickets | #19181, #17804, #19909, partially #20030 |
| License | MIT |
| Doc PR | reference to the documentation PR, if any |

Before/After:

``` diff
$ bin/console list -h
Usage:
  list [options] [--] [<namespace>]

Arguments:
  namespace            The namespace name

Options:
      --raw            To output raw command list
      --format=FORMAT  The output format (txt, xml, json, or md) [default: "txt"]
+  -h, --help            Display this help message
+  -q, --quiet           Do not output any message
+  -V, --version         Display this application version
+      --ansi            Force ANSI output
+      --no-ansi         Disable ANSI output
+  -n, --no-interaction  Do not ask any interactive question
+  -e, --env=ENV         The environment name [default: "dev"]
+      --no-debug        Switches off debug mode
+  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Help:
  The list command lists all commands:

    php bin/console list

  You can also display the commands for a specific namespace:

    php bin/console list test

  You can also output the information in other formats by using the --format option:

    php bin/console list --format=xml

  It's also possible to get raw list of commands (useful for embedding command runner):

    php bin/console list --raw
```

This could deprecate `getNativeDefinition` or make it a feature as right now it's internal and unused.

edit: resolved the BC break.

edit2: question is.. should this target 2.7?

Commits
-------

553b173 [Console] Different approach on merging application definition
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants