.. _refactoring: Refactoring *********** .. contents:: :depth: 2 :backlinks: none :local: Fixes ===== .. _refactoring_add_missing_assignements: Add Missing Assignments ----------------------- Automatically add any missing properties to a class. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor class:transform path/to/Class.php --transform=add_missing_assignments .. tab:: VIM Context Menu *Class context menu > Transform > Add missing properties*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform Motivation ~~~~~~~~~~ When authoring a class it is redundant effort to add a property and documentation tag when making an assignment. This refactoring will scan for any assignments which have do not have corresponding properties and add the required properties with docblocks based on inferred types where possible. Before and After ~~~~~~~~~~~~~~~~ .. code:: php blog = new Blog(); } } Becomes: .. code:: php blog = new Blog(); } } .. _refactoring_add_missing_docblock: Add Missing Docblock -------------------- This refactoring will add docblocks: - If there is an array return type and an iterator value can be inferred from the function's return statement. - If there is an class return type and a generic type can be inferred from the function's return statement. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor class:transform path/to/Class.php --transform=add_missing_docblocks .. tab:: VIM Context Menu *Class context menu > Transform > Add missing docblocks*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform .. tab:: Language Server Request code actions when there is a candidate .. _refactoring_complete_constructor: Add Missing Return Types ------------------------ This refactoring add missing return types. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor class:transform path/to/Class.php --transform=add_missing_return_types .. tab:: VIM Context Menu *Class context menu > Transform > Add missing return types*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform .. tab:: Language Server Request code actions when there is a candidate Complete Constructor -------------------- Complete the assignments and add properties for an incomplete constructor. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor class:transform path/to/class.php --transform=complete_constructor .. tab:: VIM Context Menu *Class context menu > Transform > Complete Constructor*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform .. _motivation-5: Motivation ~~~~~~~~~~ When authoring a new class, it is often required to: 1. Create a constructor method with typed arguments. 2. Assign the arguments to class properties. 3. Create the class properties with docblocks. This refactoring will automatically take care of 2 and 3. .. _before-and-after-5: Before and After ~~~~~~~~~~~~~~~~ .. code:: php hello = $hello; $this->goodbye = $goodbye; } } .. _refactoring_fix_namespace_and_class: Fix Namespace or Class Name --------------------------- Update a file’s namespace (and/or class name) based on the composer configuration. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor class:transform path/to/class.php --transform=fix_namespace_class_name .. tab:: VIM Context Menu *Class context menu > Transform > Fix namespace or class name*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform .. warning:: This refactoring will currently only work fully on Composer based projects. .. _motivation-6: Motivation ~~~~~~~~~~ Phpactor already has the possibility of generating new classes, and moving classes. But sometimes your project may get into a state where class-containing files have an incorrect namespace or class name. This refactoring will: - Update the namespace based on the file path (and the autoloading config). - Update the class name. - When given an empty file, it will generate a PHP tag and the namespace. .. _before-and-after-6: Before and After ~~~~~~~~~~~~~~~~ .. code:: php // lib/Barfoo/Hello.php Generate accessor*. .. tab:: VIM Plugin .. code-block:: :PhpactorGenerateAccessor .. _motivation-11: Motivation ~~~~~~~~~~ When creating entities and value objects it is frequently necessary to add accessors. This refactoring automates the generation of accessors. .. _before-and-after-11: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php bar { /** * @var Barfoo */ private $barfoo; } After selecting `one or more accessors `__ .. code:: php barfoo; } } Note the accessor template can be customized see `Templates `__. .. _generation_method: Generate Method --------------- Generate or update a method based on the method call under the cursor. .. tabs:: .. tab:: CLI *RPC only* .. tab:: VIM Context Menu *Method context menu > Generate method*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu .. _motivation-12: Motivation ~~~~~~~~~~ When initially authoring a package you will often write a method call which doesn't exist and then add the method to the corresponding class. This refactoring will automatically generate the method inferring any type information that it can. .. _before-and-after-12: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php barfoo->good<>bye($hello); } } class Barfoo { } After generating the method: .. code:: php barfoo->goodbye($hello); } } class Barfoo { public function goodbye(Hello $hello) { } } .. _generateo_constructor: Generate Constructor -------------------- Generate a constructor from a new object instance expression .. tabs:: .. tab:: LSP Invoke code action on new class expression for class with no constructor Before and After ~~~~~~~~~~~~~~~~ Assuming `MyFancyObject` exists and has no constructor. Cursor position shown as ``<>``: .. code:: php FancyObject($barfoo, 'foobar', 1234); After choosing the "Generate Constructor" code action the `MyFancyObject` class should have a constructor: .. code:: php Transform > Implement contracts*. .. tab:: VIM Plugin .. code-block:: :PhpactorTransform .. _motivation-13: Motivation ~~~~~~~~~~ It can be very tiresome to manually implement contracts for interfaces and abstract classes, especially interfaces with many methods (e.g. ``ArrayAccess``). This refactoring will automatically add the required methods to your class. If the interface uses any foreign classes, the necessary ``use`` statements will also be added. .. _before-and-after-13: Before and After ~~~~~~~~~~~~~~~~ .. code:: php `_. .. tabs:: .. tab:: LSP Invoke code action on a class which has implemented no methods and implements one or more interfaces. Before and After ~~~~~~~~~~~~~~~~ .. code:: php innerCounter = $innerCounter; } public function count(): int { return $this->innerCounter->count(); } } .. _refactoring_import_missing_class: Import Class ------------ Import a class into the current namespace based on the class name under the cursor. .. tabs:: .. tab:: VIM Plugin .. code-block:: :PhpactorImportClass .. _motivation-14: Motivation ~~~~~~~~~~ It is easy to remember the name of a class, but more difficult to remember its namespace, and certainly it is time consuming to manually code class imports: Manually one would: 1. Perform a fuzzy search for the class by its short name. 2. Identify the class you want to import. 3. Copy the namespace. 4. Paste it into your current file 5. Add the class name to the new ``use`` statement. This refactoring covers steps 1, 3, 4 and 5. .. _before-and-after-14: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php quest $request) { } } After selecting ``Symfony\Component\HttpFoundation\Request`` from the list of candidates: .. code:: php ``: .. code:: php ToNewUser::class ]; } After selecting ``App\Listeners\AssignDefaultRoleToNewUser`` from the list of candidates: .. code:: php Import Missing* .. tab:: VIM Plugin .. code-block:: :PhpactorImportMissingClasses .. _motivation-16: Motivation ~~~~~~~~~~ You may copy and paste some code from one file to another and subsequently need to import all the foreign classes into the current namespace. This refactoring will identify all unresolvable classes and import them. Fill Object ----------- Fill a new objects constructor with default arguments. .. tabs:: .. tab:: LSP Invoke code action on new class expression with no constructor arguments Motivation ~~~~~~~~~~ This refactoring is especially useful if you need to either create or map a DTO (data transfer object). Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php FancyDTO(); After choosing the "Fill Object" code action: .. code:: php Override method*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu **Multiple selection**: Supports selecting multiple methods. .. _motivation-17: Motivation ~~~~~~~~~~ Sometimes it is expected or necessary that you override a parent class's method (for example when authoring a Symfony Command class). This refactoring will allow you to select a method to override and generate that method in your class. .. _before-and-after-16: Before and After ~~~~~~~~~~~~~~~~ .. code:: php New Class* .. tab:: VIM Plugin .. code-block:: :PhpactorClassNew .. _motivation-4: Motivation ~~~~~~~~~~ Creating classes is one of the most general actions we perform: 1. Create a new file. 2. Code the namespace, ensuring that it is compatible with the autoloading scheme. 3. Code the class name, ensuring that it is the same as the file name. This refactoring will perform steps 1, 2 and 3 for: - Any given file name. - Any given class name. - A class name under the cursor. It is also possible to choose a class template, see `templates `__ for more information. .. _before-and-after-4: Before and After ~~~~~~~~~~~~~~~~ .. container:: alert alert-success This example is from an existing, empty, file. Note that you can also use the context menu to generate classes from non-existing class names in the current file Given a new file: .. code:: php # src/Blog/Post.php After invoking *class new* using the ``default`` variant: .. code:: php Copy Class* .. tab:: VIM Plugin .. code-block:: :PhpactorCopyFile .. _motivation-1: Motivation ~~~~~~~~~~ Sometimes you find that an existing class is a good starting point for a new class. In this situation you may: 1. Copy the class to a new file location. 2. Update the class name and namespace. 3. Adjust the copied class as necessary. This refactoring performs steps 1 and 2. .. _before-and-after-1: Before and After ~~~~~~~~~~~~~~~~ .. code:: php # src/Blog/Post.php Inflect > Extract interface*. .. tab:: VIM Plugin .. code-block:: :PhpactorClassInflect .. _motivation-10: Motivation ~~~~~~~~~~ It is sometimes unwise to preemptively create interfaces for all your classes, as doing so adds maintenance overhead, and the interfaces may never be needed. This refactoring allows you to generate an interface from an existing class. All public methods will be added to generated interface. .. _before-and-after-10: Before and After ~~~~~~~~~~~~~~~~ .. code:: php Change Visibility* .. tab:: VIM Plugin .. code-block:: :PhpactorChangeVisibility Currently this will cycle through the 3 visibilities: ``public``, ``protected`` and ``private``. .. _motivation-2: Motivation ~~~~~~~~~~ Sometimes you may want to extract a class from an existing class in order to isolate some of it’s responsibility. When doing this you may: 1. Create a new class using `Class New <#class-new>`__. 2. Copy the method(s) which you want to extract to the new class. 3. Change the visibility of the main method from ``private`` to ``public``. .. _before-and-after-2: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php # src/Blog/FoobarResolver.php } } After invoking “change visibility” on or within the method. .. code:: php # src/Blog/FoobarResolver.php Move Class* .. tab:: VIM Plugin .. code-block:: :PhpactorMoveFile .. _motivation-3: Motivation ~~~~~~~~~~ When authoring classes, it is often difficult to determine really appropriate names and namespaces, this is unfortunate as a class name can quickly propagate through your code, making the class name harder to change as time goes on. This problem is multiplied if you have chosen an incorrect namespace. This refactoring will move either a class, class-containing-file or folder to a new location, updating the classes namespace and all references to that class where possible in a given *scope* (i.e. files known by GIT: ``git``, files known by Composer: ``composer``, or all PHP files under the current CWD: ``simple``). If you have defined file relationships with `navigator.destinations `__, then you have the option to move the related files in addition to the specified file. If using the command then specify ``--related``, or if using the RPC interface (f.e. VIM) you will be prompted. .. container:: alert alert-danger This is a dangerous refactoring! Ensure that you commit your work before executing it and be aware that success is not guaranteed (e.g. class references in non-PHP files or docblocks are not currently updated). This refactoring works best when you have a well tested code base. .. _before-and-after-3: Before and After ~~~~~~~~~~~~~~~~ .. code:: php # src/Blog/Post.php Extract Constant*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu .. _motivation-7: Motivation ~~~~~~~~~~ Each time a value is duplicated in a class a fairy dies. Duplicated values increase the fragility of your code. Replacing them with a constant helps to ensure runtime integrity. This refactoring includes `Replace Magic Number with Symbolic Constant `__ (Fowler, Refactoring). .. _before-and-after-7: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php es') { return true; } return false; } public function yes() { return 'yes'; } } After: .. code:: php ``: .. code:: php 1 + 2 + 3 + 5 === 6) { echo 'You win!'; } After (entering ``$hasWon`` as a variable name): .. code:: php 1 + 2 + 3 + 5<> === 6) { echo 'You win!'; } After (using ``$winningCombination`` as a variable name): .. code:: php `__ or invoke it manually. .. _motivation-9: Motivation ~~~~~~~~~~ This is one of the most common refactorings. Decomposing code into discrete methods helps to make code understandable and maintainable. Extracting a method manually involves: 1. Creating a new method 2. Moving the relevant block of code to that method. 3. Scanning the code for variables which are from the original code. 4. Adding these variables as parameters to your new method. 5. Calling the new method in place of the moved code. This refactoring takes care of steps 1 through 5 and: - If a *single* variable that is declared in the selection which is used in the parent scope, it will be returned. - If *multiple* variables are used, the extracted method will return a tuple. - In both cases the variable(s) will be assigned at the point of extraction. - Any class parameters which are not already imported, will be imported. .. _before-and-after-9: Before and After ~~~~~~~~~~~~~~~~ Selection shown between the two ``<>`` markers: .. code:: php if ($foobar) { return 'yes'; } return $foobar; <> } } After extracting method ``newMethod``: .. code:: php newMethod($foobar); } private function newMethod(string $foobar) { if ($foobar) { return 'yes'; } return $foobar; } } .. _refactoring_rename_variable: Rename Variable --------------- Rename a variable in the local or class scope. .. tabs:: .. tab:: VIM Context Menu *Variable context menu > Rename*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu .. _motivation-18: Motivation ~~~~~~~~~~ Having meaningful and descriptive variable names makes the intention of code clearer and therefore easier to maintain. Renaming variables is a frequent refactoring, but doing this with a simple search and replace can often have unintended consequences (e.g. renaming the variable ``$class`` also changes the ``class`` keyword). This refactoring will rename a variable, and only variables, in either the method scope or the class scope. .. _before-and-after-17: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php os) { foreach ($hellos as $greeting) { echo $greeting; } return $hellos; } } Rename the variable ``$hellos`` to ``$foobars`` in the local scope: .. code:: php Replace references*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu .. _motivation-19: Motivation ~~~~~~~~~~ This refactoring is *similar* to `Move Class <#class-move>`__, but without renaming the file. This is a useful refactoring when a dependant library has changed a class name and you need to update that class name in your project. .. _before-and-after-18: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php lo { public function say() { } } $hello = new Hello(); $hello->say(); Rename ``Hello`` to ``Goodbye`` .. code:: php say(); .. container:: alert alert-danger When renaming classes in your project use Class Move. .. _refactoring_rename_member: Rename Class Member ------------------- Rename a class member. .. tabs:: .. tab:: CLI .. code-block:: $ phpactor references:member path/to/Class.php memberName --type="method" --replace="newMemberName" Class FQNs are also accepted .. tab:: VIM Context Menu *Member context menu > Replace references*. .. tab:: VIM Plugin .. code-block:: :PhpactorContextMenu .. _motivation-20: Motivation ~~~~~~~~~~ Having an API which is expressive of the intent of the class is important, and contributes to making your code more consistent and maintainable. When renaming members global search and replace can be used, but is a shotgun approach and you may end up replacing many things you did not mean to replace (e.g. imagine renaming the method ``name()``). This refactoring will: 1. Scan for files in your project which contain the member name text. 2. Parse all of the candidate files. 3. Identify the members, and try and identify the containing class. 4. Replace only the members which certainly belong to the target class. When replacing *private* and *protected* members, only the related classes will be updated. Due to the loosely typed nature of PHP this refactoring may not find all of the member accesses for the given class. Run your tests before and after applying this refactoring. .. container:: alert alert-info Hint: Use the CLI command to list all of the risky references. Risky references are those member accesses which match the query but whose containing classes could not be resolved. .. figure:: images/risky.png :alt: Risky references Risky references .. _before-and-after-19: Before and After ~~~~~~~~~~~~~~~~ Cursor position shown as ``<>``: .. code:: php y() { } } $hello = new Hello(); $hello->say(); Rename ``Hello#say()`` to ``Hello#speak()`` .. code:: php speak();