Skip to content

Instantly share code, notes, and snippets.

@sebsel
Last active November 5, 2020 09:32
Show Gist options
  • Save sebsel/3dce47c53ea9785f6ffb07aaa51c35ce to your computer and use it in GitHub Desktop.
Save sebsel/3dce47c53ea9785f6ffb07aaa51c35ce to your computer and use it in GitHub Desktop.
Kirby IDE Helper
<?php define('DS', DIRECTORY_SEPARATOR);
// Set this one to your own blueprint root if you changed it.
$blueprint_root = __DIR__ . DS . 'site' . DS . 'blueprints';
// For Kirby 3, see the comment below.
/**
* === Let's build an IDE-helper for Kirby for PHPStorm!
*
* You can create the IDE-helper by putting this file in your project, then run the following command:
* php _ide_helper_generator.php
*
* This will create an _ide_helper_models.php based in your blueprints and an _ide_helper.php based on
* the kirby/extensions/methods.php file (which contains field-methods). TODO: add custom field method-files.
*
* If you change your blueprints, you should run the command again.
*
* You should *NOT* add the resulting php-files to your site with `include` or `require`. They are
* only meant to be analysed by PHPStorm (or any other IDE of your choice).
*
* You could add them to Git, you could add `_ide_helper*` to your .gitignore, up to you!
*
* You can hint your IDE with `/** @var SomeblueprintPage $page * /` in your templates.
*
* Happy IDE-ing!
*/
// Import the Kirby Toolkit to make our life easier
require_once __DIR__ . DS . 'kirby' . DS . 'vendor' . DS . 'getkirby' . DS . 'toolkit' . DS . 'bootstrap.php';
$helperFile = new IDEHelperFile('_ide_helper_models.php');
foreach (dir::read($blueprint_root) as $filename) {
$blueprint = yaml::read($blueprint_root . DS . $filename);
$filename = f::name($filename);
$className = $filename == 'site' ? 'Site' : str::ucfirst($filename) . 'Page extends Page';
if (!isset($blueprint['fields'])) {
$helperFile->addEmpty($className);
continue;
}
foreach (array_keys($blueprint['fields']) as $field) {
$helperFile->add($className, $field, 'Field');
}
}
$helperFile->save();
/**
* Now for Kirby's build in magic methods
*/
$helperFile = new IDEHelperFile('_ide_helper.php');
// Yeah, you can add your Field method-files to this array
$fieldMethodsFiles[] = __DIR__ . DS . 'kirby' . DS . 'extensions' . DS . 'methods.php';
class Field { public static $methods; }
$fieldMethodsReturnTypes = [];
foreach ($fieldMethodsFiles as $methodsFile) {
require_once $methodsFile;
// Just regex them.
preg_match_all(
'/\@return (.*?)\n \*\/\nfield::\$methods\[\'(.*?)\'\]/',
f::read($methodsFile),
$matches,
PREG_SET_ORDER
);
foreach ($matches as $match) {
$return = $match[1];
$method = $match[2];
$fieldMethodsReturnTypes[$method] = $return;
}
}
foreach (field::$methods as $name => $method) {
$func = new ReflectionFunction($method);
$args = array_map(function ($arg) { return $arg->name; }, $func->getParameters());
$return = array_key_exists($name, $fieldMethodsReturnTypes)
? $fieldMethodsReturnTypes[$name] : '';
$helperFile->add('Field', $name, $return, $args);
}
// Also add the v:: validator class
foreach (v::$validators as $name => $validator) {
$func = new ReflectionFunction($validator);
$args = array_map(function ($arg) { return $arg->name; }, $func->getParameters());
$helperFile->add('V', $name, 'boolean', $args, true);
}
$helperFile->save();
/**
* Helpers
*/
class IDEHelperFile {
private $filename;
private $methods = [];
public function __construct($filename)
{
$this->filename = $filename;
}
public function add($class, $method, $return, $args = [], $static = false)
{
$this->methods[$class][$method] = [
'return' => $return,
'static' => $static,
'args' => $args,
];
}
public function addEmpty($class) {
$this->methods[$class] = [];
}
public function save()
{
$helper = "<?php\n";
foreach ($this->methods as $class => $methods) {
$helper .= "\n/**\n";
foreach ($methods as $method => $d) {
$helper .= ' * @method ';
$helper .= $d['static'] ? 'static' : '';
$helper .= $d['return'] . " $method(";
$helper .= $d['args'] ? '$' . implode(', $', $d['args']) : '';
$helper .= ")\n";
}
$helper .= " */\nclass $class {}\n";
}
f::write($this->filename, $helper);
}
}
@sascha-schieferdecker
Copy link

I had problems running this with Kirby 3.3.4 so I modified this with using the autoloading of composer. Maybe it helps someone trying to start Kirby development with PHPStorm.

<?php define('DS', DIRECTORY_SEPARATOR);

// Set this one to your own blueprint root if you changed it.
$blueprint_root = __DIR__ . DS . 'site' . DS . 'blueprints/pages';

/**
 * === Let's build an IDE-helper for Kirby for PHPStorm!
 *
 * You can create the IDE-helper by putting this file in your project, then run the following command:
 *   php _ide_helper_generator.php
 *
 * This will create an _ide_helper_models.php based in your blueprints and an _ide_helper.php based on
 * the kirby/extensions/methods.php file (which contains field-methods). TODO: add custom field method-files.
 *
 * If you change your blueprints, you should run the command again.
 *
 * You should *NOT* add the resulting php-files to your site with `include` or `require`. They are
 * only meant to be analysed by PHPStorm (or any other IDE of your choice).
 *
 * You could add them to Git, you could add `_ide_helper*` to your .gitignore, up to you!
 *
 * You can hint your IDE with `/** @var SomeblueprintPage $page * /` in your templates.
 *
 * Happy IDE-ing!
 */

// Import the Kirby Toolkit to make our life easier

$autoloader = __DIR__ . '/vendor/autoload.php';

require($autoloader);

$helperFile = new IDEHelperFile('_ide_helper_models.php');

foreach (\Kirby\Toolkit\Dir::read($blueprint_root) as $filename) {
    $blueprint = \Kirby\Data\Yaml::read($blueprint_root . DS . $filename);

    $filename = \Kirby\Toolkit\F::name($filename);
    $className = $filename == 'site' ? 'Site' : str::ucfirst($filename) . 'Page extends \Kirby\Cms\Page';

    if (!isset($blueprint['fields'])) {
        $helperFile->addEmpty($className);
        continue;
    }

    foreach (array_keys($blueprint['fields']) as $field) {
        $helperFile->add($className, $field, 'Field');
    }
}

$helperFile->save();

/**
 * Now for Kirby's build in magic methods
 */

$helperFile = new IDEHelperFile('_ide_helper.php');

$fieldMethodsFiles = [];
// Yeah, you can add your Field method-files to this array
//$fieldMethodsFiles[] = __DIR__ . DS . 'kirby' . DS . 'extensions' . DS . 'methods.php';


class Field { public static $methods; }
$fieldMethodsReturnTypes = [];
foreach ($fieldMethodsFiles as $methodsFile) {
    require_once $methodsFile;

    // Just regex them.
    preg_match_all(
        '/\@return (.*?)\n \*\/\nfield::\$methods\[\'(.*?)\'\]/',
        f::read($methodsFile),
        $matches,
        PREG_SET_ORDER
    );

    foreach ($matches as $match) {
        $return = $match[1];
        $method = $match[2];
        $fieldMethodsReturnTypes[$method] = $return;
    }
}

if (is_iterable(field::$methods)) {
    foreach (field::$methods as $name => $method) {
        $func = new ReflectionFunction($method);
        $args = array_map(function ($arg) { return $arg->name; }, $func->getParameters());
        $return = array_key_exists($name, $fieldMethodsReturnTypes)
            ? $fieldMethodsReturnTypes[$name] : '';

        $helperFile->add('Field', $name, $return, $args);
    }
}

// Also add the v:: validator class
foreach (v::$validators as $name => $validator) {
    $func = new ReflectionFunction($validator);
    $args = array_map(function ($arg) { return $arg->name; }, $func->getParameters());

    $helperFile->add('V', $name, 'boolean', $args, true);
}

$helperFile->save();


/**
 * Helpers
 */
class IDEHelperFile {

    private $filename;
    private $methods = [];

    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    public function add($class, $method, $return, $args = [], $static = false)
    {
        $this->methods[$class][$method] = [
            'return' => $return,
            'static' => $static,
            'args' => $args,
        ];
    }

    public function addEmpty($class) {
        $this->methods[$class] = [];
    }

    public function save()
    {
        $helper = "<?php\n";

        foreach ($this->methods as $class => $methods) {
            $helper .= "\n/**\n";

            foreach ($methods as $method => $d) {
                $helper .= ' * @method ';
                $helper .= $d['static'] ? 'static' : '';
                $helper .= $d['return'] . " $method(";
                $helper .= $d['args'] ? '$' . implode(', $', $d['args']) : '';
                $helper .=  ")\n";
            }

            $helper .= " */\nclass $class {}\n";
        }

        \Kirby\Toolkit\F::write($this->filename, $helper);
    }
}

@sebsel
Copy link
Author

sebsel commented Feb 19, 2020

The irony is that I'm back to writing my code in VIM, so I'm not actually using this myself anymore. Thanks for sharing this new version! :)

@Alzy
Copy link

Alzy commented Nov 5, 2020

I've run this and everything but I'm not really seeing anything new PHPstorm.... is there any other project configuration I need to do on my part with PHPstorm to get hints to show up? Or do these not work on template pages?

@sascha-schieferdecker
Copy link

You have to give PhpStorm some type hints, then it works fine, like this for example:

Page template

<?php snippet('header') ?>
<?php
/** @var $site \Kirby\Cms\Site */
/** @var $page DefaultPage */
/** @var $pages \Kirby\Cms\Pages */
?>
<?php foreach ($page->children()->listed() as $part) : ?>
  <!-- <?php echo $part->intendedTemplate() ?> -->
    <?php snippet($part->intendedTemplate(), ['part' => $part]) ?>
  <!-- / <?php echo $part->intendedTemplate() ?> -->
<?php endforeach ?>
<?php snippet('footer-start') ?>
<?php snippet('offcanvas') ?>
<?php snippet('footer') ?>

Snippet:

<?php
/* @var $part \Kirby\Cms\Page */
/* @var $site \Kirby\Cms\Site */
?>

<div class="top-wrap uk-position-relative uk-light" id="top">
  <?php snippet('menu') ?>
  <div class="uk-light uk-flex uk-flex-middle top-wrap-height" id="introtext">

    <!-- TOP CONTAINER -->
    <div class="uk-container uk-flex uk-flex-right top-container uk-position-relative uk-margin-medium-top" data-uk-parallax="y: 0,50; easing:0; opacity:0.2">
      <div class="uk-width-1-2@s uk-padding" data-uk-scrollspy="cls: uk-animation-slide-right-medium; target: > *; delay: 150">
        <h1 class="uk-margin-remove-top"><?php echo $part->content()->get('heading') ?></h1>
        <p class="subtitle-text uk-visible@s"><?php echo $part->content()->get('text')->nl2br() ?></p>
        <div class="uk-text-right">
          <a href="<?php echo $part->content()->get('link')->slug() ?>" title="Mehr erfahren"
             class="uk-button uk-button-primary uk-border-pill" data-uk-scrollspy-class="uk-animation-fade">
            <div class="uk-flex uk-flex-middle">
              <div>
                <?php echo $part->linktext() ?>
              </div>
              <div>
                <span uk-icon="icon: chevron-right; ratio: 2"></span>
              </div>
            </div>
          </a>

        </div>
      </div>
    </div>
    <!-- /TOP CONTAINER -->
    <!-- TOP IMAGE -->
    <?php if ($file = $part->content()->get('images')->first()->toFile()): ?>
    <img srcset="<?= $file->srcset('default') ?>" src="<?php echo $file->url()?>" alt="" data-uk-cover data-uk-img>
    <?php endif ?>
    <!-- /TOP IMAGE -->
  </div>
</div>

@Alzy
Copy link

Alzy commented Nov 5, 2020

So if one of my page blueprints was in say site/blueprints/pages/team-member.yml I would say something like:

/** @var $page TeamMemberPage */

<div class="content-wrapper">
    <h1>
        <?= $page->title() ?>
    </h1>

    ...
</div>

I think I'm following.. Just wanna make sure I got a grip on this

@sascha-schieferdecker
Copy link

Only if you really have PHP class named TeamMemberPage. Otherwise use DefaultPage or whatever is listed in _ide_helper_models.php.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment