メインコンテンツに移動

shirane lab

メインナビゲーション

  • ホーム
  • ブログ
  • Drush
  • 検索

パンくず

  • ホーム
  • ブログ
  • drush php:cli と psysh について

drush php:cli と psysh について

2021/12/11(土) - 00:16
shirane

この記事は Drupal Advent Calendar 2021 の11日目です。


ちょっとしたコードを CLI で動かしたい時に便利な環境が REPL です。Drupal 9 では、Drush の php:cli コマンドで PsySh という PHP の REPL が利用できます。

インストールと実行

Drush の依存関係に PsySh が含まれているので、Composer で Drush を入れるだけで使えます。

ただし、2021年12月5日にリリースされた最新の PsySh v0.11 が導入されると、現状の Drush ではエラーが発生します。このため、いま現在 Drush の新規インストールやアップデートを行うと php:cli コマンドが動作しない状態に陥る可能性があります。

修正のプルリクが出ていますが、反映されるまでの回避策として、PsySh の旧 0.10 版を手動で入れる方法が考えられます。PsySh v0.10.12 を上書きインストールする例を示します。


$ cd <プロジェクトのルート>
$ curl -L https://github.com/bobthecow/psysh/archive/refs/tags/v0.10.12.tar.gz | tar xzf - --strip=1 -C vendor/psy/psysh

追記(2021-12-17)
Drush 10.6.2 で psysh への依存関係が次のように変更されて解決しました。

"psy/psysh": ">=0.6 <0.11",

Drush をアップデートしてみたところ、psysh がダウングレードされ、drush php:cli コマンドで正常に対話シェルが起動することを確認しました。

$ composer update drush/drush -w
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 2 updates, 0 removals
  - Upgrading drush/drush (10.6.1 => 10.6.2)
  - Downgrading psy/psysh (v0.11.0 => v0.10.12)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 2 updates, 0 removals
  - Downgrading psy/psysh (v0.11.0 => v0.10.12): Extracting archive
  - Upgrading drush/drush (10.6.1 => 10.6.2): Extracting archive
Package doctrine/reflection is abandoned, you should avoid using it. Use roave/better-reflection instead.
Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead.
Generating autoload files
45 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
$

導入されている PsySh のバージョンが 0.10.x であれば正常に起動します。

$ drush php:cli
Psy Shell v0.10.12 (PHP 7.4.3 — cli) by Justin Hileman
DEMO site (Drupal 9.3.0)
>>>

起動したら、プロンプト(>>>)に PHP コードを入力して、変数、関数、クラスなどを定義できます。

>>> $msg = 'Hello!';
=> "Hello!"
>>> function greet($peer) {
...   echo "Hi, $peer!";
...   return $peer;
... }
>>> class Person {
...   private $name = '';
...   
...   public function __construct($name) {
...     $this->name = $name;
...   }
...   
...   public function greet($peer) {
...     echo "Hi $peer, I'm $this->name!";
...     return $this->name;
...   }
... }
>>> 

式を入力すると評価結果が表示され、ステートメントを入力すると実行されます。

>>> $msg
=> "Hello!"
>>> greet("Jiro");
Hi, Jiro!⏎
=> "Jiro"
>>> $taro = new Person("Taro");
=> Person {#4524}
>>> $taro->greet("Jiro");
Hi Jiro, I'm Taro!⏎
=> "Taro"
>>> 

PsySh の機能

PsySh manual に詳しい説明があります。以下、一部を紹介します。

マジック変数

最後に使用した値が情報の種類別に保持されており、これをマジック変数($ で始まる定義済みの変数)で取得することができます。

>>> $_    // 最後の結果を保持するマジック変数
=> "Taro"
>>> greet('Hanako');
Hi, Hanako!⏎
=> "Hanako"
>>> $_
=> "Hanako"
>>> 

PsySh コマンド

PsySh は種々の組み込みコマンドを備えています。たとえば、ls コマンドを実行すると、ローカル変数やインスタンス等の一覧が表示されます。

>>> ls 
Variables: $container, $msg, $taro
>>> ls -al

Variables:
  $container   Drupal\Core\DependencyInjection\Container {#768 …620}  
  $msg         "Hello!"                                               
  $taro        Person {#4524}                                         
  $_           "Hanako"                                               
  $__out       "Hi, Hanako!" 

各コマンドの詳しい情報は、help コマンドで確認できます。

>>> help ls
Usage:
 ls [--vars] [-c|--constants] [-f|--functions] [-k|--classes] [-I|--interfaces] [-t|--traits] [--no-inherit] [-p|--properties] [-m|--methods] [-G|--grep GREP] [-i|--insensitive] [-v|--invert] [-g|--glob>

Aliases: dir

Arguments:
 target             A target class or object to list.

Options:
 --vars             Display variables.
 --constants (-c)   Display defined constants.
 --functions (-f)   Display defined functions.
 --classes (-k)     Display declared classes.
 --interfaces (-I)  Display declared interfaces.
 --traits (-t)      Display declared traits.
 --no-inherit       Exclude inherited methods, properties and constants.
 --properties (-p)  Display class or object properties (public properties by default).
 --methods (-m)     Display class or object methods (public methods by default).
 --grep (-G)        Limit to items matching the given pattern (string or regex).
 --insensitive (-i) Case-insensitive search (requires --grep).
 --invert (-v)      Inverted search (requires --grep).
 --globals (-g)     Include global variables.
 --internal (-n)    Limit to internal functions and classes.
 --user (-u)        Limit to user-defined constants, functions and classes.
 --category (-C)    Limit to constants in a specific category (e.g. "date").
 --all (-a)         Include private and protected methods and properties.
 --long (-l)        List in long format: includes class names and method signatures.
 --help (-h)        Display this help message.

Help:
 List variables, constants, classes, interfaces, traits, functions, methods,
 and properties.
 
 Called without options, this will return a list of variables currently in scope.
 
 If a target object is provided, list properties, constants and methods of that
 target. If a class, interface or trait name is passed instead, list constants
 and methods on that class.
 
 e.g.
 >>> ls
 >>> ls $foo
 >>> ls -k --grep mongo -i
 >>> ls -al ReflectionClass
 >>> ls --constants --category date
 >>> ls -l --functions --grep /^array_.*/
 >>> ls -l --properties new DateTime()

dump コマンドは、オブジェクトや基本型の値をダンプします。

>>> dump -a $taro
Person {#4524
  -name: "Taro",
}
>>> 

doc コマンドを使用すると、PHP のオブジェクト、クラス、定数、メソッド、プロパティのドキュメントを閲覧できます。ただし、事前に PHP マニュアルをダウンロードしてインストールしておく必要があります。日本語版をインストールする例を示します。

$ wget http://psysh.org/manual/ja/php_manual.sqlite -P ~/.local/share/psysh

以後は、調べたいものを引数に指定して doc コマンドを実行すると、そのマニュアルが表示されます。

>>> doc implode
function implode($glue, $pieces)

Description:
  配列要素を文字列により連結する
  
  配列の要素を $glue 文字列で連結します。
  
  implode()は、歴史的な理由により、引数をどちら の順番でも受けつけることが可能です。しかし、
  explode() との統一性の観点からは、 ドキュメントに記述された引数の順番を使用しないことは推奨されま
  せん。

Param:
  string  $glue    デフォルトは空文字列です。
  array   $pieces  連結したい文字列の配列。

Return:
  string  すべての配列要素の順序を変えずに、各要素間に $glue 文字列をはさんで 1 つの文字列にして返し
          ます。

See Also:
   * explode()
   * preg_split()
   * http_build_query()

>>> 

シェルコマンドの実行

PsySh の中からシェルのコマンドを実行することもできます。実行するコマンドラインをバッククォートで囲みます。コマンドの実行結果は、文字列のリテラル("〜")として表示されます。

>>> `pwd`
=> "/var/www/hoge/web\n"
>>> `ls -l`
=> """
   total 84\n
   -rw-rw-r-- 1 vagrant vagrant   315 Dec  8 12:05 autoload.php\n
   drwxrwxr-x 1 vagrant vagrant  4096 Nov 24 23:27 core\n
   -rw-rw-r-- 1 vagrant vagrant  1507 Nov 27 17:27 example.gitignore\n
   -rw-rw-r-- 1 vagrant vagrant   549 Nov 27 17:27 index.php\n
   -rw-rw-r-- 1 vagrant vagrant    94 Nov 27 17:27 INSTALL.txt\n
   drwxrwxr-x 1 vagrant vagrant  4096 Dec  8 12:05 modules\n
   drwxrwxr-x 1 vagrant vagrant  4096 Nov 27 17:27 profiles\n
   -rw-r--r-- 1 vagrant vagrant 12484 Dec  8 21:14 q\n
   -rw-rw-r-- 1 vagrant vagrant  3205 Nov 27 17:27 README.md\n
   -rw-rw-r-- 1 vagrant vagrant  1586 Nov 27 17:27 robots.txt\n
   drwxrwxr-x 1 vagrant vagrant  4096 Dec  8 11:59 sites\n
   drwxrwxr-x 1 vagrant vagrant  4096 Nov 27 17:27 themes\n
   -rw-r--r-- 1 vagrant vagrant 12484 Dec  8 14:28 t q\n
   -rw-rw-r-- 1 vagrant vagrant   804 Nov 27 17:27 update.php\n
   -rw-rw-r-- 1 vagrant vagrant  4016 Nov 27 17:27 web.config\n
   """
>>> 

Drupal における利用

Drush から起動した PsySh のプロンプトは、Drupal で必要なファイルがすべて読み込まれた状態になっており、モジュール内のコードと同じように Drupal API を呼び出してサイトの操作や情報取得ができます。

// Drupal の構成設定 API でサイト名を取得する例
>>> \Drupal::config('system.site')->get('name');
=> "DEMO site"

// settings.php で設定された $settings['hash_salt'] の値を取得する例
>>> \Drupal\Core\Site\Settings::get('hash_salt');
=> "7bb8S5Y7pZ4tvwciwBR8jsk0Ol2_Piy38vErPATD1TEPwjX0CpZLWkzgaXdALjEcP_ohmiJzkw"

    ls コマンドを実行すると、Drupal\Core\DependencyInjection\Container 型の $container という変数が利用できることがわかります。

    >>> ls -l
    
    Variables:
      $container   Drupal\Core\DependencyInjection\Container {#768 …604}  
    >>> 
    

    このオブジェクトの get() メソッドを利用して Drupal のコアサービスのインスタンスを取得できます。entity_type.manager サービスの例を示します。

    // entity_type.manager サービスを取得する例
    >>> $manager = $container->get('entity_type.manager');
    => Drupal\Core\Entity\EntityTypeManager {#943
         +"_serviceId": "entity_type.manager",
       }
    

    取得したインスタンスの型の詳細は doc コマンドで閲覧できます。

    >>> doc $manager
    class Drupal\Core\Entity\EntityTypeManager extends Drupal\Core\Plugin\DefaultPluginManager implements Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface, Drupal\Core\Cache\CacheableDependencyInterface, Drupal\Core\Entity\EntityTypeManagerInterface, Symfony\Component\DependencyInjection\ContainerAwareInterface
    
    Description:
      Manages entity type plugin definitions.
      
      Each entity type definition array is set in the entity type's annotation and
      altered by hook_entity_type_alter().
      
      Do not use hook_entity_type_alter() hook to add information to entity types,
      unless one of the following is true:
      - You are filling in default values.
      - You need to dynamically add information only in certain circumstances.
      - Your hook needs to run after hook_entity_type_build() implementations.
      Use hook_entity_type_build() instead in all other cases.
    
    See: \Drupal\Core\Entity\Annotation\EntityType
    See: \Drupal\Core\Entity\EntityInterface
    See: \Drupal\Core\Entity\EntityTypeInterface
    See: hook_entity_type_alter()
    See: hook_entity_type_build()
    

    ls コマンドを使用すると、クラスのメンバを調べることができます。

    >>> ls -al $manager;
    
    Class Properties:
      $additionalAnnotationNamespaces        []                                   >
      $alterHook                             "entity_type"                        >
      $cacheBackend                          Drupal\Core\Cache\ChainedFastBackend >
      $cacheKey                              "entity_type"                        >
      $cacheTags                             [ …1]                                >
      $classResolver                         Drupal\Core\DependencyInjection\Class>
      $container                             Drupal\Core\DependencyInjection\Conta>
    …
    
    Class Methods:
      alterDefinitions                protected function alterDefinitions(&$defini>
      alterInfo                       protected function alterInfo($alter_hook)   >
      cacheGet                        protected function cacheGet($cid)           >
      cacheSet                        protected function cacheSet($cid, $data, $ex>
      clearCachedDefinitions          public function clearCachedDefinitions()    >
      createHandlerInstance           public function createHandlerInstance($class>
      createInstance                  public function createInstance($plugin_id, a>
      doGetDefinition                 protected function doGetDefinition(array $de>
      extractProviderFromDefinition   protected function extractProviderFromDefini>
      findDefinitions                 protected function findDefinitions()        >
      getAccessControlHandler         public function getAccessControlHandler($ent>
      getActiveDefinition             public function getActiveDefinition($entity_>
      getCacheContexts                public function getCacheContexts()          >
      getCachedDefinitions            protected function getCachedDefinitions()   >
      getCacheMaxAge                  public function getCacheMaxAge()            >
      getCacheTags                    public function getCacheTags()              >
      getDefinition                   public function getDefinition($entity_type_i>
      getDefinitions                  public function getDefinitions()            >
      getDiscovery                    protected function getDiscovery()           >
      getFactory                      protected function getFactory()             >
      getFormObject                   public function getFormObject($entity_type_i>
      getHandler                      public function getHandler($entity_type_id, >
      getInstance                     public function getInstance(array $options) >
      getListBuilder                  public function getListBuilder($entity_type_>
      getRouteProviders               public function getRouteProviders($entity_ty>
      getStorage                      public function getStorage($entity_type_id) >
    …
    

    doc コマンドは、メソッドの詳細の確認にも利用できます。

    >>> doc $manager->getStorage
    class Drupal\Core\Entity\EntityTypeManager extends Drupal\Core\Plugin\DefaultPluginManager implements Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface, Drupal\Core\Cache\CacheableDependencyInterface, Drupal\Core\Entity\EntityTypeManagerInterface, Symfony\Component\DependencyInjection\ContainerAwareInterface
    public function getStorage($entity_type_id)
    
    Description:
      {@inheritdoc}
    
    ---
    
    interface Drupal\Core\Entity\EntityTypeManagerInterface extends Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface, Drupal\Component\Plugin\Discovery\DiscoveryInterface, Drupal\Component\Plugin\Factory\FactoryInterface, Drupal\Component\Plugin\Mapper\MapperInterface, Drupal\Component\Plugin\PluginManagerInterface
    abstract public function getStorage($entity_type_id)
    
    Description:
      Creates a new storage instance.
    
    Throws:
      \Drupal\Component\Plugin\Exception\PluginNotFoundException           Thrown if the entity type doesn't exist.
      \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException  Thrown if the storage handler couldn't be loaded.
    
    Param:
      string  $entity_type_id  The entity type ID for this storage.
    
    Return:
      \Drupal\Core\Entity\EntityStorageInterface  A storage instance.
    

    こうした情報を確認しながら、種々の API を呼び出していくことができます。

    // uid=1 のユーザーを読み込む例
    >>> $user = $manager->getStorage('user')->load(1);
    => Drupal\user\Entity\User {#5435
         #uid: Drupal\Core\Field\FieldItemList {#5370},
         #uuid: Drupal\Core\Field\FieldItemList {#5573},
         #langcode: Drupal\Core\Field\FieldItemList {#5579},
         #preferred_langcode: Drupal\Core\Field\FieldItemList {#5593},
         #preferred_admin_langcode: Drupal\Core\Field\FieldItemList {#5607},
         #name: Drupal\Core\Field\FieldItemList {#5609},
         #pass: Drupal\Core\Field\FieldItemList {#5615},
         #mail: Drupal\Core\Field\FieldItemList {#5625},
         #timezone: Drupal\Core\Field\FieldItemList {#5631},
         #status: Drupal\Core\Field\FieldItemList {#5637},
         #created: Drupal\Core\Field\FieldItemList {#5643},
         #changed: Drupal\Core\Field\ChangedFieldItemList {#5649},
         #access: Drupal\Core\Field\FieldItemList {#5655},
         #login: Drupal\Core\Field\FieldItemList {#5661},
         #init: Drupal\Core\Field\FieldItemList {#5667},
         #roles: Drupal\Core\Field\EntityReferenceFieldItemList {#5673},
         #default_langcode: Drupal\Core\Field\FieldItemList {#5685},
         #user_picture: Drupal\file\Plugin\Field\FieldType\FileFieldItemList {#5690},
       }
    
    // インスタンスの内容をダンプする例
    >>> dump -a $user;
    Drupal\user\Entity\User {#5435
      #values: [
        "uid" => [
          "x-default" => "1",
        ],
        "uuid" => [
          "x-default" => "fa02f5db-396d-48af-b8f4-256ab018db5d",
        ],
        "langcode" => [
          "x-default" => "ja",
        ],
        "preferred_langcode" => [
          "x-default" => "ja",
        ],
        "preferred_admin_langcode" => [
          "x-default" => null,
        ],
        "name" => [
          "x-default" => "admin",
        ],
        "pass" => [
          "x-default" => "$S$Ee85r8WTkOUqZHp1oIxIlig5fOntFC8DrfyOoq.93TE9ezcWD9jO",
        ],
        "mail" => [
          "x-default" => "admin@example.com",
        ],
        "timezone" => [
          "x-default" => "Asia/Tokyo",
        ],
        "status" => [
          "x-default" => "1",
        ],
        "created" => [
          "x-default" => "1639116396",
        ],
    :

    名前空間を指定すると、クラス等の指定を簡略化できます。

    >>> namespace Drupal\user\Entity
    >>> User::load(1)
    => Drupal\user\Entity\User {#5435
    …

    show コマンドを利用して、特定のメソッドのコードを閲覧することもできます。

    >>> show User::load
    481:   /**
    482:    * {@inheritdoc}
    483:    */
    484:   public static function load($id) {
    485:     $entity_type_repository = \Drupal::service('entity_type.repository');
    486:     $entity_type_manager = \Drupal::entityTypeManager();
    487:     $storage = $entity_type_manager->getStorage($entity_type_repository->>
    488:     return $storage->load($id);
    489:   }
    

    プロジェクトを常に PhpStorm 等の IDE で開いて xdebug している場合は必要ないかもしれませんが、ターミナルでサイトを調査する時などは便利に活用できそうです。

    参考資料

    • psysh
    • Drepl
    • drush php:cli コマンド
    • bobthecow/psysh
    • PsySH manual
    • phpのREPL psysh が便利そうだった
    • Interactive PHP Debugging with PsySH

    追記:

    PsySh のブレークポイントってこれかな。eval(\Psy\sh()) の先で \Psy\debug() が呼ばれて、そこで Shell のインスタンスの run() を呼ぶんですね。https://t.co/XF0ySUBaj9

    — Kenji Shirane (@bkenro) January 13, 2022
    Drupal9
    Drush
    REPL
    psysh
    ‹ 前の記事次の記事 ›

    書籍

    『D9 おいしいレシピ集2』がパワーアップして商業誌に

    『D9 おいしいレシピ集2』がパワーアップして商業誌に

     書籍の一覧はこちら

     

    タグ一覧

    DrushDrupal9Drupal6ffdsmVagrant開発環境VirtualBoxDrupal7ComposerコミュニティDrupal5Migrate勉強会モジュールDocker書籍ubercartWindowsCKEditorArtisteerDrupal ONSENDrupal4MariaDBTwigデバッグthemingTomeテーマH5PインストールAnalytics仮想マシンCentOSMacREPLpsyshDruxtJSデカップルドヘッドレスNuxtQuizVue

    サイト運営

    シナジークエスト

    © shirane lab