Skip to content

PHP frameworks and libraries

Ben Mewburn edited this page Aug 15, 2024 · 2 revisions

Intelephense aims to support all frameworks but does not implement framework specific solutions. Intelephense does not run any PHP code and instead statically analyses a project. Limited or unexpected language intelligence can sometimes be provided if the framework:

  • Declares symbols at runtime via bootstrapping code or configuration.
  • Uses interfaces heavily but encourages calling methods only declared on implementations.
  • Uses __get/__call/__callStatic magic heavily.
  • Has insufficient or incorrect type declarations/annotations.

In such cases you may notice lack of completion suggestions, trouble jumping to definitions or undefined symbol diagnostics may appear even though the code may work when executed.

For example, a common problem can be when a framework returns an interface from a helper method but the project has been bootstrapped to use a particular concrete type that has additional methods not declared on the interface.

<?php

interface View {}

class CustomView implements View
{
  public function customViewMethod() {}
}

function view(): View
{
  //some code that happens to return CustomView at runtime based on some bootstrapping code or config
}

view()->customViewMethod(); //undefined method 😭

There are several ways to workaround the problem above. These workarounds can fall into two categories. Either they become part of the project executable code itself, or they are are declared in a non-executable helper file and are there only to override the default Intelephense behaviour.

Solutions that form part of the exectuable code

The advantage here is that problems in the code would become more apparent if the bootstrapping logic ever changed and returned a different class. The disadvantage is it's more code to write and perhaps difficult to retrofit to existing code.

<?php

//Assign the return value to a variable and narrow the type
$view = view();
if (! $view instanceof CustomView) {
  throw new Exception('Unexpected View instance');
}
$view->customViewMethod();

//Or with an annotation.
//This won't alter the execution of the code but still involves modifying the executable code.

/** @var CustomView $view */
$view = view();
$view->customViewMethod();

//A custom helper could also be created and called instead to narrow the type
function customView(): CustomView
{
  $view = view();
  assert($view instanceof CustomView);
  return $view;
}

customView()->customViewMethod();

Solutions that do not form part of the project executable code and only help Intelephense

The advantage here is that it can be retrofitted easily to existing code, applies to all usages of the symbol and executable code remains untouched. The disadvantage is that it could suppress an actual error that Intelephense would otherwise detect.

<?php
//Create a helper file and add it to your workspace. eg intelephense_helper.php

//Declare a different signature for the view function. One that declares the concrete return type.
function view(): CustomView {}

//Or add the undefined method to the interface instead.
interface View
{
  function customViewMethod();
}

Using the helper file approach above can be useful in overriding any vendor symbol declarations as intelephense will prioritise project declared symbols over vendor declared symbols.

If classes, interfaces or traits have override definitions then intelephense will treat them as partial types and merge them with the vendor declared types. Overrides should not extend or implement different classes or interfaces to the vendor declarations as implements and extends values are not merged.

There are also packages that provide or generate IDE helper files that may improve the experience when using various frameworks and libraries. For example: https://github.com/barryvdh/laravel-ide-helper

Clone this wiki locally