Using the example of PhpStorm 2020.1 and a state-of-the-art CakePHP 4 app.

Preliminary remarks

The following ideally should adhere to any IDE and framework. I just can’t verify and confirm them all, thus the selection at hand.

Please be so kind to either submit your confirmations and additions at the bottom as comments or better yet directly to the plugin itself.

The wiki contains a list of supported IDEs and tips.

Why an IDE?

Some might want to save money, others like to work "low-level", almost command-like-level. And as long as you get the job done, there is nothing against this.

But I bet high-level IDEs are usually for everyone a bit more productive and faster as they provide a lot of things for you out of the box, from type-hinting and autocompletion to basic return type checks, refactoring tools and other extras.

Beginners:

Ideally, you get a lot of tips and proposals and what you can do and select (method list dropdowns, etc). So as long as you don’t know the language and framework perfectly out of the box, or where to quickly lookup those things, then you could actually be 10x as fast using a proper high-level IDE that is tailored to the language and framework.

Veterans:

Here, you usually already know exactly what you want. The IDE here can prevent some common mistakes or hint on certain issues before you run the code. Yes, even pros can make silly mistakes 🙂

But more importantly, it just helps you to speed up things. By making sure most operations are hinted and autocomplete, you could just need a serious of [Enter], [Arrow down], etc and you should be able to write code also 5x as fast as with normal "glorified text editors".

Use doc blocks

This is probably the most basic thing of them all. It shouldn’t be necessary to write this down, but I still see a lot of undocumented or badly/partially documented code.

But this is the very first step to let the IDE help you. The most important parts here for IDEs to be most helpful are the @param and @return tags.

If you specify a return value as @return array and then use the return value somewhere as string, the IDE will mark it yellow, for a good reason.

Pro-tip: Use phpstan and alike to combine the doc blocks with actual code analyzing for automatic and maximum sanity checking. Fewer errors here means more reliable code.

Now with PHP7+, many functions/methods are now type hinted itself through code (type-hinting and return type-hinting).

That does not mean you should get rid of the documentation, though.

It is free to get the basics rolled out (CS sniff/fix), and it is then trivial to add notes where needed or even customize the type. E.g.

/** * @param string|null $plugin * @return string[] */ public function paths(?string $plugin): array { return ... }

As you can see, the type-hint "array" does not properly reflect the internals of it (array of strings), which can be very important when iterating over it.

Only if documented more strictly, the IDE, as well as static analyzing, will be able to fully help here in detecting issues early on.

Rule of thumb for textual additions: Don’t repeat what’s already there or clearly readable from code. Give valuable additional information, instead.

You want to be as short and precise as possible here.

Use annotations

The more "magic" a framework or library ships with, the more important these annotations become.

Properties

So if you, for example, have a registry that loads a locatable class in your system, you would want to annotate those properties if they are auto-provided for you.

Inside a controller:

/** * @return void */ public function initialize(): void { parent::initialize(); $this->loadComponent('Calendar.Calendar'); }

Now, you could e.g. call $this->Calendar->init() somewhere along the controller action code.

But the IDE wouldn’t know this object and that there is such a method available. Highly error-prone for humans then, and no help from the IDE.

Let’s add the annotation for it:

/** * @property \Calendar\Controller\Component\CalendarComponent $Calendar */ class CalendarController extends AppController {}

Now the IDE will be able to help you and even follow that method. With one click you can look into it and see what it does.

Methods

The same can be useful for methods:

/** * @method \App\Model\Entity\User get($primaryKey, $options = []) */ class UsersTable extends Table {}

Without this annotation, the ->get() call will annotate as EntityInterface .

So if you want to then call any custom method on it, it would not be proposed by the IDE.

Once you have the overrides in place here, it will all work flawlessly out of the box.

Tip: Use the annotator here to always keep your code in sync:

bin/cake annotate all -v -d

Leverage IDE refactoring

This is the point where I say: Try to use as fewer magic strings as possible, especially for code-tied strings.

Refactoring like renaming with [Shift + F6] works best across the whole codebase if you use class constants or alike instead.

Then you don’t run into the danger of forgetting a few pieces, as the IDE can find and rename all occurrences for you.

This doesn’t go for all kinds of magic strings. I will outline a few useful ones to think about next.

Flags and Options

If you have a dry-run option for your CLI command or a forceRefresh one for your cache method, you most likely re-use that a few times throughout the code and the tests for it.

So here, it can make sense to "class const" them:

public const OPTION_DRY_RUN = 'dry-run'; $this->doSth($value, static::OPTION_DRY_RUN);

public const OPTION_FORCE_REFRESH = 'forceRefresh'; $this->doSth($value, [static::OPTION_FORCE_REFRESH => true]);

It helps to not have to remember the exact casing or spelling (scheme vs schema sometimes even) and reduces typo mistakes through autocomplete.

On top of that also speeds it up usually.

The biggest benefit here, though, is the additional IDE support you can get on refactoring. You can see the usage of that constant and better assess all necessary locations to adjust along with your change. Some can even be done completely by IDE support (e.g. rename).

Finite string choices

In my entities, I often have to code the field access more dynamically.

So I need to use $this->setDirty() and alike with a string field name.

But here, you would have to manually type the whole field, sometimes not fully knowing all choices or where they are used.

Here I use the IdeHelper File Illuminator to make all possible fields visual through exposed class constants.

// old $carEntity->setDirty('wheels'); // new $carEntity->setDirty($carEntity::FIELD_WHEELS);

Any change in field name or existence would directly be visible through those class constants now in use throughout the code. Magic strings have to direct attachment and only will become visible when executing that piece of code afterward, either through test coverage or real-life usage.

A similar thing in state machine processes and available state names. See this on how it solved this using class constants generated from the set of available states at that time, and how to keep it sync moving forward.

Danger-Zone: Overkill

A few words to the danger of shooting over the goal.

If you now dig in and blindly remove all magic strings, that very likely goes way beyond any usefulness.

Exception messages (only ever for devs), single-use strings of no practical re-usefulness, regex patterns, and alike end up often being less useful in some class const than inline code.

Instead, focus on the actual strings that matter and are highly reused, as those are the ones you will see the most benefit with.

Generate a meta-file

Wherever you need to work with magic strings and (static) factory calls, it is indispensable to have basic type-hinting and auto-completion support.

In the paragraph before we talked about reducing magic strings. But not necessarily all need replacement.

If we leverage the IDEs specific meta file capabilities as in this case using the .meta.php file, some of those "pure magic strings" become now "less evil magic".

We basically want to cross over from the Voldemort magic to the Harry Potter one for those wherever possible.

To do this, I will quickly outline the main meta directives at hand we can use for this:

Override (input together with return type/value)

ExpectedArguments (one specific argument of a function/method)

ExpectedReturnValues

For more details, I refer to the docs and the examples you can work through.

Note for completeness: There is also an "auto-complete" file that makes the IDE understand available properties on class objects.

There is an own section of the IdeHelper on this if you are interested.

Conventions

In modern frameworks, some basic strings often reflect locatable class names.

So in the Cake world Tools.Tree helper reflects the Tools\View\Helper\TreeHelper class.

Now, in most of these cases, you could use the FQCN here, and you might be around the same time typing it. You might even have IDE usage stats on it now.

Depending on your knowledge you might still not know if the selection is actually valid in this context, and as for FQCN, the more common the class name, the more results you have to look through to find the right match.

This example shows two things even:

One is a very speedy way to select the right table class we want to load from a short list of choices.

The other is the $branchesTable now being "annotated" as the concrete object the directive defines instead of the generic table object they all inherit.

So if you continue coding and try to call a specific table method, it will now correctly type-hint and autocomplete for you:

$branches->executeSpecificMethod(...);

Note: This is not too helpful for static analyzers like PHPStan usually, they require the annotating docblock here.

Options

I will give an easy one here:

public function requirePresence(string $field, $mode = true) {} // validation setup ->requirePresence('my_field', ...) // true/'update'/'create'

You can either require the field always or just on update/create cases.

Now we could class const it, but at the same time you would probably need

use statement import for the FQCN class name

still have to know the possible options if there are different class constants inside the class

What alternatives would we have around magic string usage?

This is the meta definition PhpStorm understands:

expectedArguments( \Cake\Validation\Validator::requirePresence(), 1, 'create', 'update' );

Now, if you type the opening single-quote ' for the 2nd parameter, you get already autocompleted the list of possible choices for this position 1 (zero-based position).

There are tons more of these use cases, see the tasks available inside the IdeHelper itself, or even the additional extra tasks available in IdeHelperExtra.

You can PR more useful ones for PHP, CakePHP, or the ecosystem in general (plugins and beyond).

Note: As of right now, the meta functionality is not yet fully developed and many useful choices for values, e.g. FQCN class names, are not yet supported.

So in these cases it is even a benefit to use strings here still if they represent the same as the actual class name. You need to decide on each use case.

More useful scenarios

Code translations are a classic magic string issue.

Here it can have huge impacts on your speed when you happen to re-use a lot of them:

__('Open this dialog')

These can now be autocomplete using the meta-file generator.

Same for pretty much all commonly used methods that get a finite list of string choices, like

->setLayout('custom')

And of course, your own plugins and application code sure has also a lot of these examples to offer.

What about…

Yeah, there is a lot of specific IDE built-in tooling, as well. I didn’t want to drill into all sub-topics, so I just picked a few interesting ones that haven’t been looked into that much maybe. We can collect those remaining ones as comments or eventually as follow-up article.

Summary

It might be useful to check what your IDE is capable of and if that suffices your needs. What features would really speed up your efficiency?

During the post, I then outlined a few different ways to think about magic strings and if/how they could either be replaced or enhanced to make you more productive and the code less error-prone when changing. There is not always one perfect answer, often it is a balance of pros and cons, of simplicity over sophistication, of short term and long term benefits.

It is now up to you to check for yourself and apply some of them where applicable. Where do you want to see FQCN class name linking, where class constants and where to keep the simple magic strings in place?

In the end, you want to

Reach a good understanding for static analyzers to automatically introspect your code regularly to spot possible bugs early on

Reach maximum usability and coding speed – as the development time factor is often the most expensive in coding. If you are more productive, everyone is happier.

Use an IDE and tooling around it that supports you here

And check out the IdeHelper plugin if you didn’t have the chance yet.

Happy coding!

Event note

I will present the IDE specifics (and a bit more) as live demo session at this week’s International Community Meetup (April 29th).

Join in if you’d like.