Jump to content

PhpStorm autocompletion and typehinting of wire('xx')


interrobang
 Share

Recommended Posts

In PhpStorm you can get autocompletion and typehinting for wire function calls if you add a file with the following content somewhere to your project. Jetbrains suggests naming this file '.phpstorm.meta.php' and adding it to the root of your project, but it should work anywhere else too. I had to re-open the project after adding the file.


<?php
/**
* ProcessWire PhpStorm Meta
*
* This file is not a CODE, it makes no sense and won't run or validate
* Its AST serves PhpStorm IDE as DATA source to make advanced type inference decisions.
*
* @see https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata
*/


namespace PHPSTORM_META {

$STATIC_METHOD_TYPES = [
\wire('') => [
'' == '@',
'config' instanceof Config,
'wire' instanceof ProcessWire,
'log' instanceof WireLog,
'notices' instanceof Notices,
'sanitizer' instanceof \Sanitizer,
'database' instanceof \WireDatabasePDO,
'db' instanceof \DatabaseMysqli,
'cache' instanceof \MarkupCache,
'modules' instanceof \Modules,
'procache' instanceof \ProCache,
'fieldtypes' instanceof \Fieldtypes,
'fields' instanceof \Fields,
'fieldgroups' instanceof \Fieldgroups,
'templates' instanceof \Templates,
'pages' instanceof \Pages,
'permissions' instanceof \ Permissions,
'roles' instanceof \Roles,
'users' instanceof \Users,
'user' instanceof \User,
'session' instanceof \Session,
'input' instanceof \WireInput,
'languages' instanceof \Languages,
'page' instanceof \Page,
]
];
}



  • Like 8
Link to comment
Share on other sites

Good find @interrobang. Thanks for posting. I like how this narrows it down because before I would get every method of every API variable possible, which worked just fine, but this makes it even simpler. A couple of questions that I wasn't able to figure out from the linked page, and thought you might know: 

1. How to make this apply to $this->wire() in the same way? (i.e. The wire() method from the Wire class). I tried to do it like this, duplicating the same properties, but no luck:

 \Wire::wire('') => [ all the same properties here ]

2. Also trying to figure out how to make it recognize a wire() or $this>wire() call with no arguments returns an instance of class ProcessWire. Any ideas? I'm not sure I fully follow what the '' == '@' means, the doc page is a little unclear, or maybe it's time to refill my coffee. 

  • Haha 1
Link to comment
Share on other sites

Unfortunately I can't help here - I did not understand much of the documentation. Getting to this point was mostly trial and error.

I am not even sure if this can be achieved with this file. I think the main purpose of the meta file is to enable type-hinting for static factory methods. 

Link to comment
Share on other sites

  • 9 years later...

Wow, this post is already 9 years old! You can do even more today. I just looked at the file together with claude.ai and here is a new suggestion:

<?php

namespace PHPSTORM_META {

    override(\ProcessWire\Wire::wire(0), map([
        'config' => \ProcessWire\Config::class,
        'cache' => \ProcessWire\WireCache::class,
        'wire' => \ProcessWire\ProcessWire::class,
        'log' => \ProcessWire\WireLog::class,
        'notices' => \ProcessWire\Notices::class,
        'sanitizer' => \ProcessWire\Sanitizer::class,
        'database' => \ProcessWire\WireDatabasePDO::class,
        'db' => \ProcessWire\DatabaseMysqli::class,
        'modules' => \ProcessWire\Modules::class,
        'fieldtypes' => \ProcessWire\Fieldtypes::class,
        'fields' => \ProcessWire\Fields::class,
        'fieldgroups' => \ProcessWire\Fieldgroups::class,
        'templates' => \ProcessWire\Templates::class,
        'pages' => \ProcessWire\Pages::class,
        'permissions' => \ProcessWire\Permissions::class,
        'roles' => \ProcessWire\Roles::class,
        'users' => \ProcessWire\Users::class,
        'user' => \ProcessWire\User::class,
        'session' => \ProcessWire\Session::class,
        'input' => \ProcessWire\WireInput::class,
        'languages' => \ProcessWire\Languages::class,
        'page' => \ProcessWire\Page::class,
        'hooks' => \ProcessWire\WireHooks::class,
        'files' => \ProcessWire\WireFileTools::class,
        'datetime' => \ProcessWire\WireDateTime::class,
        'mail' => \ProcessWire\WireMailTools::class,
        '' => '@'
    ]));

    // Alternative method using expectedArguments and expectedReturnValues
    expectedArguments(\ProcessWire\Wire::wire(), 0, 
        'config', 'cache', 'wire', 'log', 'notices', 'sanitizer', 'database', 
        'db', 'modules', 'fieldtypes', 'fields', 'fieldgroups', 'templates', 
        'pages', 'permissions', 'roles', 'users', 'user', 'session', 'input', 
        'languages', 'page', 'hooks', 'files', 'datetime', 'mail'
    );

    expectedReturnValues(\ProcessWire\Wire::wire(), 
        \ProcessWire\Config::class,
        \ProcessWire\WireCache::class,
        \ProcessWire\ProcessWire::class,
        \ProcessWire\WireLog::class,
        \ProcessWire\Notices::class,
        \ProcessWire\Sanitizer::class,
        \ProcessWire\WireDatabasePDO::class,
        \ProcessWire\DatabaseMysqli::class,
        \ProcessWire\Modules::class,
        \ProcessWire\Fieldtypes::class,
        \ProcessWire\Fields::class,
        \ProcessWire\Fieldgroups::class,
        \ProcessWire\Templates::class,
        \ProcessWire\Pages::class,
        \ProcessWire\Permissions::class,
        \ProcessWire\Roles::class,
        \ProcessWire\Users::class,
        \ProcessWire\User::class,
        \ProcessWire\Session::class,
        \ProcessWire\WireInput::class,
        \ProcessWire\Languages::class,
        \ProcessWire\Page::class,
        \ProcessWire\WireHooks::class,
        \ProcessWire\WireFileTools::class,
        \ProcessWire\WireDateTime::class,
        \ProcessWire\WireMailTools::class
    );
}

But there's more, here claude helped me to write a little module that adds a few things to phpstorm.meta.php. After installing the module you get suggestions for 
`$fields->get('')` // suggests existing field names
`$templates->get('')` // suggests existing template names
`$modules->get('')` // suggests existing module names 

<?php namespace ProcessWire;

class MetaFileGenerator extends WireData implements Module, ConfigurableModule
{
    public static function getModuleInfo() {
        return [
            'title'    => 'PhpStorm Meta File Generator',
            'version'  => 1,
            'summary'  => 'Generates .phpstorm.meta.php file for ProcessWire autocompletion',
            'singular' => true,
            'autoload' => 'template=admin',
            'requires' => 'ProcessWire>=3.0.0',
        ];
    }

    public function init() {
        $this->addHookAfter('Fields::save', $this, 'generateMetaFile');
        $this->addHookAfter('Fields::delete', $this, 'generateMetaFile');
        $this->addHookAfter('Templates::save', $this, 'generateMetaFile');
        $this->addHookAfter('Templates::delete', $this, 'generateMetaFile');
        $this->addHookAfter('Modules::install', $this, 'generateMetaFile');
        $this->addHookAfter('Modules::uninstall', $this, 'generateMetaFile');
    }

    public function ready() {
        if ($this->wire()->session->metaFileGenerate) {
            $this->generateMetaFile();
            $this->wire()->session->remove('metaFileGenerate');
        }
    }

    public function ___install() {
        $this->wire()->session->set('metaFileGenerate', true);
    }

    static public function getModuleConfigInputfields(array $data) {
        $inputfields = new InputfieldWrapper();
        $modules = wire()->modules;

        // Add button to regenerate meta file
        $f = $modules->get('InputfieldButton');
        $f->name = 'regenerate';
        $f->value = __('Regenerate Meta File');
        $f->icon = 'refresh';
        $f->href = wire()->config->urls->admin.'module/edit?name=MetaFileGenerator&regenerate=1';
        $inputfields->add($f);

        // Add note about current meta file
        $f = $modules->get('InputfieldMarkup');
        $f->name = 'meta_file_info';
        $filePath = wire()->config->paths->get('MetaFileGenerator').'.phpstorm.meta.php';
        $fileExists = file_exists($filePath);
        $lastModified = $fileExists ? date('Y-m-d H:i:s', filemtime($filePath)) : 'never';
        $f->value = "<p>Meta File Location: <code>{$filePath}</code><br>Last Modified: {$lastModified}</p>";
        $inputfields->add($f);

        // Check if regeneration was requested
        $module = $modules->get('MetaFileGenerator');
        if (wire()->input->get('regenerate')) {
            $module->generateMetaFile();
            wire()->session->message(__('Meta file regenerated'));
            wire()->session->redirect(wire()->config->urls->admin.'module/edit?name=MetaFileGenerator');
        }

        return $inputfields;
    }

    public function generateMetaFile(HookEvent $event = null) {
        $content = "<?php\n\nnamespace PHPSTORM_META {\n\n";

        // Add Module autocompletion
        $content .= $this->generateModuleMetaContent();

        // Add Field autocompletion
        $content .= $this->generateFieldMetaContent();

        // Add Template autocompletion
        $content .= $this->generateTemplateMetaContent();

        $content .= "}\n";

        try {
            $file = $this->wire()->config->paths->get($this->className()).'.phpstorm.meta.php';
            if (file_put_contents($file, $content) !== false) {
                if ($event) {
                    $this->message('PhpStorm meta file updated successfully');
                }
            } else {
                $this->error('Failed to write meta file');
            }
        } catch (\Exception $e) {
            $this->error($e->getMessage());
        }
    }

    protected function generateModuleMetaContent() {
        $content = "    // Module name autocompletion for get() and install()\n";
        $content .= "    override(\\ProcessWire\\Modules::get(0), map([\n";

        $allModules = $this->wire('modules')->findByPrefix('');
        $moduleData = [];

        foreach ($allModules as $moduleName) {
            $moduleClass = strpos($moduleName, '\\') === false
                ? "\\ProcessWire\\{$moduleName}"
                : $moduleName;
            $moduleData[$moduleName] = "        '$moduleName' => $moduleClass::class,\n";
        }

        ksort($moduleData);
        $content .= implode('', $moduleData);
        $content .= "    ]));\n\n";

        return $content;
    }

    protected function generateFieldMetaContent() {
        $content = "    // Field name autocompletion for Fields::get()\n";
        $content .= "    override(\\ProcessWire\\Fields::get(0), map([\n";

        foreach ($this->wire()->fields as $field) {
            $content .= "        '{$field->name}' => '\\ProcessWire\\Field',\n";
        }

        $content .= "    ]));\n\n";

        return $content;
    }

    protected function generateTemplateMetaContent() {
        $content = "    // Template name autocompletion for Templates::get()\n";
        $content .= "    override(\\ProcessWire\\Templates::get(0), map([\n";

        foreach ($this->wire()->templates as $template) {
            $content .= "        '{$template->name}' => '\\ProcessWire\\Template',\n";
        }

        $content .= "    ]));\n\n";

        return $content;
    }
}

 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...