Hi internals,
php -a
currently provides an interactive shell with tab completion written in C, in readline/libedit.
This currently has a number of limitations:
-
Adding new features or bug fixes would require a time getting familiar with
C programming, PHP's internals and memory management, and with how readline works internally.Making it possible to write replace more of the REPL implementation in PHP instead of C
may open this up to more contributors.
(at the cost of a completion performance slowdown/memory increase/installation size,
which should be unimportant if only some interactive shells are affected or if there are few candidates for completion) -
Completions for syntax such as constants, member variables/properties, etc. (
$x->
) have been a TODO for years.
// ext/readline/readline_cli.c
static char *cli_completion_generator(const char *text, int index) /* {{{ */
{
/*
TODO:
- constants
- maybe array keys
- language constructs and other things outside a hashtable (echo, try, function, class, ...)
- object/class members
- future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
*/
So, my ideas for improving the situation in 8.1 or later
-
Allow overriding
readline_completion_function()
in auto_prepend_file
(I think that that not working is a bug, and hope it could get fixed in 8.0)
https://github.com/php/php-src/pull/5872https://github.com/phan/phan/commit/228827516df606de23e9ac94873c7b953f4bf4c1 (
tool/phan_repl_helpers.php
) includes a prototype
of replacing PHP's C readline completion with a readline callback written in pure PHP.
(I started working on that file today and it's missing some completion functionality, but it may be a good starting point) -
Add a hook to
readline
such asreadline_set_php_result_handler_callback(function(mixed $result, string $snippet){...})
that can be used to create a user-defined function to print/process the result of expressions.
(Create a clone ofzend_eval_stringl
to support that)
(The result value would instead be freed after the callback was called)Many other REPLs (Read-Eval-Print Loops) that I'm familiar with print a representation of the result of expressions, but PHP doesn't.
Some values such as $GLOBALS, extremely long binary strings, etc. may need to have the representation truncated in some way.
A tiny example implementation ofreadline_set_php_result_handler_callback
would beif ($result !== null) { var_dump($result); }
-
Add a hook such as
readline_before_evaluate_php_snippet(string $snippet) : bool {}
that gets called before attempting to evaluate snippets (whether or not they have parse errors).Returning true would indicate that the snippet should not be evaluated.
This could be useful for adding meta commands that don't use PHP syntax, such as
help;
help SomeClass::someClassElement;
,cd /dirname
, etc. -
Add some way to provide the contents of all previous lines during
php -a
to provide better completions for multi-line snippets
(e.g. suggest local variables in multi-line function declarations instead of global variables)
Finally, it would be useful to have something to tie those together:
- Provide an
iphp
,phpi
,php-interactive
, or some other entry point to prepend a bundled phar script
that uses those hooks to provide a better user experience.
(Analogous to howipython
andirb
start a different interactive shell thanpython
orruby
)
Miscellaneous thoughts on implementation details:
- Bundling an actual parser (e.g. https://github.com/nikic/PHP-Parser) would help in properly analyzing
Foo::$var-><TAB>
by being less reliant on heuristics (e.g. checking if $var was a variable or a property, making it easier to collect local variables, etc).
Is packaging a parser practical for a phpi
binary (e.g. for package managers, maintainers of php, other reasons)?
-
A parser may fail for code using new token types until the parser gets updated to handle the new token types. This stops being a concern after feature freezes.
Looping over@token_get_all()
and bailing out on an unknown token type may help with that. -
How would crash/bug fixes of
phpi
or the parser be handled in patch releases of php if this was released with php? -
Automatically rewriting the code to namespace the parser and its dependencies with
\PHP\Internal\BundledPhpParser
would letphpi
be used with projects that depend on a different php-parser version.(clarifications may be necessary to indicate to end users that the bundled parser copy won't get updates or support outside of php minor releases,
should not be used by libraries/applications and that it won't support newer php syntax, and possibly other clarifications) -
It may be useful to have an ini setting to disable these new hooks,
in case bugs/crashes in libraries using those hooks interfered with debugging.
P.S. What do developers here use for an interactive shell?
I've seen https://github.com/bobthecow/psysh mentioned as an alternative for php -a
while investigating options but I haven't gotten around to using it.
It's useful that php -a
doesn't die from otherwise unrecoverable errors.
Thanks,
- Tyson
P.S. What do developers here use for an interactive shell?
I've seen https://github.com/bobthecow/psysh mentioned as an alternative forphp -a
while investigating options but I haven't gotten around to using it.
I’m a big fan of PsySH. I use it in all my projects. It supports all or most of the features you’ve mentioned, and it’s the project used as the base for Laravel Tinker, Drupal shell, WP-CLI shell for WordPress, CakePHP console, and Yii shell.
It seems to be the de facto REPL of choice with the PHP community. :-)
Cheers,
Ben
Hi internals,
- Add a hook to
readline
such asreadline_set_php_result_handler_callback(function(mixed $result, string $snippet){...})
that can be used to create a user-defined function to print/process the result of expressions.
(Create a clone ofzend_eval_stringl
to support that)
(The result value would instead be freed after the callback was called)Many other REPLs (Read-Eval-Print Loops) that I'm familiar with print a representation of the result of expressions, but PHP doesn't.
I've added this in https://github.com/php/php-src/pull/5962 .
I plan to add a system ini setting that can be used to permanently disable this silently (enabled by default in all environments/example configs)
This will be callable if readline
is installed, whether or not the shell is interactive or the SAPI is CLI.
- e.g.
cli.allow_interactive_shell_result_function=1
would be the default. Other existing settings were https://www.php.net/manual/en/readline.configuration.php - A buggy handler closure might interfere with people trying to debug issues,
which is why the ini setting is proposed.
readline_interactive_shell_result_function(
function (string $code, $result) {
// echo "Result of: " . trim($code) . "\n";
echo json_encode($result) . "\n"; // or `var_dump()`, etc.
});
- Are there objections to adding this in 8.1 (after adding the described ini setting)?
- Thoughts on the name? readline_interactive_shell_result_handler_function probably makes more sense.
https://www.php.net/manual/en/function.readline-completion-function.php does something similar.
The unit tests give an example of how this could be used.
https://github.com/php/php-src/pull/5962/files#diff-153ee3f384d333904c5000033bee9803
Thanks,
- Tyson