Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PhpStorm - Add a corybantic avalanche of type-hints #27179

Merged
merged 9 commits into from Aug 27, 2023
43 changes: 43 additions & 0 deletions tools/extensions/phpstorm/Civi/PhpStorm/Api3Generator.php
@@ -0,0 +1,43 @@
<?php

namespace Civi\PhpStorm;

use Civi\Core\Service\AutoService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* @service civi.phpstorm.api3
*/
class Api3Generator extends AutoService implements EventSubscriberInterface {

public static function getSubscribedEvents() {
return [
'civi.phpstorm.flush' => 'generate',
];
}

public function generate() {
/*
* FIXME: PHPSTORM_META doesn't seem to support compound dynamic arguments
* so even if you give it separate lists like
* ```
* expectedArguments(\civicrm_api4('Contact'), 1, 'a', 'b');
* expectedArguments(\civicrm_api4('Case'), 1, 'c', 'd');
* ```
* It doesn't differentiate them and always offers a,b,c,d for every entity.
* If they ever fix that upstream we could fetch a different list of actions per entity,
* but for now there's no point.
*/

$entities = \civicrm_api3('entity', 'get', []);
$actions = ['create', 'delete', 'get', 'getactions', 'getcount', 'getfield', 'getfields', 'getlist', 'getoptions', 'getrefcount', 'getsingle', 'getunique', 'getvalue', 'replace', 'validate'];

$builder = new PhpStormMetadata('api3', __CLASS__);
$builder->registerArgumentsSet('api3Entities', ...$entities['values']);
$builder->registerArgumentsSet('api3Actions', ...$actions);
$builder->addExpectedArguments('\civicrm_api3()', 0, 'api3Entities');
$builder->addExpectedArguments('\civicrm_api3()', 1, 'api3Actions');
Comment on lines +38 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you've defined a reusable set I guess it wouldn't hurt to add civicrm_api as well (it's basically v3-only).

Suggested change
$builder->addExpectedArguments('\civicrm_api3()', 0, 'api3Entities');
$builder->addExpectedArguments('\civicrm_api3()', 1, 'api3Actions');
$builder->addExpectedArguments('\civicrm_api3()', 0, 'api3Entities');
$builder->addExpectedArguments('\civicrm_api3()', 1, 'api3Actions');
$builder->addExpectedArguments('\civicrm_api()', 0, 'api3Entities');
$builder->addExpectedArguments('\civicrm_api()', 1, 'api3Actions');

I like how I've been busy ripping out APIv3 examples at the same time you were adding this. Yin an Yang 🙃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colemanw lol. Yeah... I like to the think that the net-change is positive (total size of removed doc-examples exceeds extra size of these hints).

It is easy to add civicrm_api(), and it's an acceptable cost. But I suppose #27180 gives a rationale for why we might not care to?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things that make you go "meh"

$builder->write();
}

}
44 changes: 44 additions & 0 deletions tools/extensions/phpstorm/Civi/PhpStorm/Api4Generator.php
@@ -0,0 +1,44 @@
<?php

namespace Civi\PhpStorm;

use Civi\Core\Service\AutoService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* @service civi.phpstorm.api4
*/
class Api4Generator extends AutoService implements EventSubscriberInterface {

public static function getSubscribedEvents() {
return [
'civi.phpstorm.flush' => 'generate',
'hook_civicrm_post::CustomGroup' => 'generate',
];
}

public function generate() {
/*
* FIXME: PHPSTORM_META doesn't seem to support compound dynamic arguments
* so even if you give it separate lists like
* ```
* expectedArguments(\civicrm_api4('Contact'), 1, 'a', 'b');
* expectedArguments(\civicrm_api4('Case'), 1, 'c', 'd');
* ```
* It doesn't differentiate them and always offers a,b,c,d for every entity.
* If they ever fix that upstream we could fetch a different list of actions per entity,
* but for now there's no point.
*/

$entities = \Civi\Api4\Entity::get(FALSE)->addSelect('name')->execute()->column('name');
$actions = ['get', 'save', 'create', 'update', 'delete', 'replace', 'revert', 'export', 'autocomplete', 'getFields', 'getActions', 'checkAccess'];

$builder = new PhpStormMetadata('api4', __CLASS__);
$builder->registerArgumentsSet('api4Entities', ...$entities);
$builder->registerArgumentsSet('api4Actions', ...$actions);
$builder->addExpectedArguments('\civicrm_api4()', 0, 'api4Entities');
$builder->addExpectedArguments('\civicrm_api4()', 1, 'api4Actions');
$builder->write();
}

}
50 changes: 50 additions & 0 deletions tools/extensions/phpstorm/Civi/PhpStorm/EventGenerator.php
@@ -0,0 +1,50 @@
<?php

namespace Civi\PhpStorm;

use Civi\Core\CiviEventDispatcher;
use Civi\Core\CiviEventDispatcherInterface;
use Civi\Core\CiviEventInspector;
use Civi\Core\Service\AutoService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* @service civi.phpstorm.event
*/
class EventGenerator extends AutoService implements EventSubscriberInterface {

public static function getSubscribedEvents() {
return [
'civi.phpstorm.flush' => 'generate',
];
}

public function generate() {
$inspector = new CiviEventInspector();

$entities = \Civi\Api4\Entity::get(FALSE)->addSelect('name')->execute()->column('name');
$specialEvents = ['hook_civicrm_post', 'hook_civicrm_pre', 'civi.api4.validate'];
foreach ($entities as $entity) {
foreach ($specialEvents as $specialEvent) {
$entityEvents [] = "$specialEvent::$entity";
}
}
// PHP 7.4 can simplify:
// $entityEvents = array_map(fn($pair) => implode('::', $pair), \CRM_Utils_Array::product([$entities, $specialEvents]));


$all = array_merge(array_keys($inspector->getAll()), $entityEvents);

$builder = new PhpStormMetadata('events', __CLASS__);
$builder->registerArgumentsSet('events', ...$all);

foreach ([CiviEventDispatcher::class, CiviEventDispatcherInterface::class] as $class) {
foreach (['dispatch', 'addListener', 'removeListener', 'getListeners', 'hasListeners'] as $method) {
$builder->addExpectedArguments(sprintf("\\%s::%s()", $class, $method), 0, 'events');
}
}

$builder->write();
}

}
24 changes: 24 additions & 0 deletions tools/extensions/phpstorm/Civi/PhpStorm/PhpStormMetadata.php
Expand Up @@ -44,6 +44,30 @@ public function __construct(string $name, string $attribution) {
$this->buffer = '';
}

public function registerArgumentsSet(string $name, ...$args) {
$escapedName = var_export($name, 1);
$escapedArgs = implode(', ', array_map(function($arg) {
return var_export($arg, 1);
}, $args));
$this->buffer .= "registerArgumentsSet($escapedName, $escapedArgs);\n";
return $this;
}

/**
* @param string $for
* Ex: '\Civi\Core\SettingsBag::get()'
* @param int $index
* The positional offset among the arguments
* @param string $argumentSet
* Name of the argument set. (This should already be defined by `registerArgumentsSet()`.)
* @return $this
*/
public function addExpectedArguments(string $for, int $index, string $argumentSet) {
$escapedSet = var_export($argumentSet, 1);
$this->buffer .= "expectedArguments($for, $index, argumentsSet($escapedSet));\n";
return $this;
}

/**
* @param string $for
* @param array $map
Expand Down
28 changes: 28 additions & 0 deletions tools/extensions/phpstorm/Civi/PhpStorm/SettingsGenerator.php
@@ -0,0 +1,28 @@
<?php

namespace Civi\PhpStorm;

use Civi\Core\Service\AutoService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* @service civi.phpstorm.settings
*/
class SettingsGenerator extends AutoService implements EventSubscriberInterface {

public static function getSubscribedEvents() {
return ['civi.phpstorm.flush' => 'generate'];
}

public function generate() {
$metadata = \Civi\Core\SettingsMetadata::getMetadata();
$methods = ['get', 'getDefault', 'getExplicit', 'getMandatory', 'hasExplicit', 'revert', 'set'];
$builder = new PhpStormMetadata('settings', __CLASS__);
$builder->registerArgumentsSet('settingNames', ...array_keys($metadata));
foreach ($methods as $method) {
$builder->addExpectedArguments('\Civi\Core\SettingsBag::' . $method . '()', 0, 'settingNames');
}
$builder->write();
}

}
1 change: 1 addition & 0 deletions tools/extensions/phpstorm/info.xml
Expand Up @@ -34,5 +34,6 @@
<mixin>mgd-php@1.0.0</mixin>
<mixin>setting-php@1.0.0</mixin>
<mixin>smarty-v2@1.0.1</mixin>
<mixin>scan-classes@1.0.0</mixin>
</mixins>
</extension>
11 changes: 10 additions & 1 deletion tools/extensions/phpstorm/phpstorm.php
Expand Up @@ -27,13 +27,22 @@ function phpstorm_metadata_dir(): string {
* @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config/
*/
function phpstorm_civicrm_config(&$config): void {
_phpstorm_civix_civicrm_config($config);
_phpstorm_civix_civicrm_config($config);
}

function phpstorm_civicrm_container(\Symfony\Component\DependencyInjection\ContainerBuilder $container) {
$container->addCompilerPass(new \Civi\PhpStorm\PhpStormCompilePass(), PassConfig::TYPE_AFTER_REMOVING, 2000);
}

function phpstorm_civicrm_managed(&$entities, $modules) {
// We don't currently have an event for extensions to join the "system flush" operation. Apply Skullduggery method.
// This gives a useful baseline event for most generators -- but it's _not_ for "services", and each generator may be supplemented
// by other events.
if ($modules === NULL && !defined('CIVICRM_TEST')) {
Civi::dispatcher()->dispatch('civi.phpstorm.flush');
}
}

function phpstorm_civicrm_uninstall() {
$dir = phpstorm_metadata_dir();
if (file_exists($dir)) {
Expand Down