Skip to content

Using ide-helper outside the framework with just illuminate/database #756

@mfn

Description

@mfn

I'm creating this issue because maybe it helps others which are interested in a similar solution and maybe there's interest in making the ide-helper code slightly more agnostic.

My scenario:

But working with models without ide-helper is very annoying (also for static analysis like phpstan) so I was seeking a solution to make it possible to use ide-helper for two things:

  • models phpdoc generation
  • metadata generation for PhpStorm

I did not evaluate the generation of Facade data as I'm not using them.

To make the models command work

  • use of base_path() global helper from the framework
    In \Barryvdh\LaravelIdeHelper\Console\ModelsCommand::loadModels it uses the global helper base_path() which isn't available outside of the framework (it's defined in vendor/laravel/framework/src/Illuminate/Foundation/helpers.php)
    • My solution was to write my own global base_path() which I only load in the context of generating the models which creates me the correct path
  • Use of the Config service
    Multiple times the $this->laravel['config'] service is access to load the configuration. However I'm not using illuminate/config either. I've created a one-shot anonymous class with my code which emulates the most basic methods to make this work. I used below code and and bound it to my container via $laravelContainer->instance('config', …); in below code Arr::get can be used because of the dependencies of other packages to illuminate/support:
            return new class($config) implements ArrayAccess {
                /** @var array */
                private $config;
    
                public function __construct(array $config)
                {
                    $this->config = $config;
                }
    
                public function get($key, $defaultVaue = null)
                {
                    return Arr::get($this->config, $key, $defaultVaue);
                }
    
                public function offsetExists($offset)
                {
                    throw new RuntimeException('Not implemented because we didn\'t need it yet');
                }
    
                public function offsetGet($offset)
                {
                    return $this->get($offset);
                }
    
                public function offsetSet($offset, $value)
                {
                    throw new RuntimeException('Not implemented because we didn\'t need it yet');
                }
    
                public function offsetUnset($offset)
                {
                    throw new RuntimeException('Not implemented because we didn\'t need it yet');
                }
            };
    • Of course this means you need to somehow provide the configuration to the class which is custom in my case
  • The final bootstrap to invoke the command was this:
    $fileSystem = new Filesystem();
    $command = new ModelsCommand($fileSystem);
    $command->setLaravel($laravelContainer);
    $command->run(
      new ArrayInput(['--write' => true, '--reset' => true]),
      new ConsoleOutput()
    );

To make the meta command work

  • I used the same config approach as outlined above
  • The command uses the https://github.com/illuminate/view component to render the final metafile which I don't use, either. I therefore created a minimal anonymous class I passed to the MetaCommand constructor; it's looks like a hack but works :
    return new class() {
        public function make(string $view, array $bindings)
        {
            return new class($view, $bindings) {
                private $view;
                private $bindings;
    
                public function __construct(string $view, array $bindings)
                {
                    $this->view = $view;
                    $this->bindings = $bindings;
                }
    
                public function render(): string
                {
                    $viewFile = base_path("vendor/barryvdh/laravel-ide-helper/resources/views/$this->view.php");
                    extract($this->bindings, EXTR_SKIP);
    
                    ob_start();
                    require $viewFile;
    
                    return ob_get_clean();
                }
            };
        }
    };
  • Additionally, I've my own ::make() method which I wanted to add to the generated file. I couldn't find a way to change \Barryvdh\LaravelIdeHelper\Console\MetaCommand::$methods so I created my own MyMetaCommand extends MetaCommand class and just override the protected $methods property
  • After that, the final bootstrap call looks like this:
    $fileSystem = new Filesystem();
    $command = new MyMetaCommand($fileSystem, $viewClass, $configClass);
    $command->setLaravel($laravelContainer);
    $command->run(
        new ArrayInput([]),
        new ConsoleOutput()
    );

Finishing words

I didn't really knew upfront if I could make it work. I'm really happy it did, having known the ide-helper for years form real Laravel project I truly didn't wanted to miss it!

I think the only suggestions I would have:

  • don't use base_path() inside \Barryvdh\LaravelIdeHelper\Console\ModelsCommand, but pass it as string $basePath property to the commands constructor via \Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider
  • Provide a way to alter \Barryvdh\LaravelIdeHelper\Console\MetaCommand::$methods once the command is created.
  • Unsolved: in \Barryvdh\LaravelIdeHelper\Console\ModelsCommand::createPhpDocs the @mixin \Eloquent is harcoded. I'm not even using Facades so if I still would would the mixin I would need them to point both the Eloquent\BuilderandQuery\Builder`; I left it as it for now

Otherwise: really very great package and I hope I continues to work also on the standalone versions in the future.

PS: feel free to close the issue, I merely wanted to document it for my future self/others

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions