juphyter

Guide

Quarto

A .qmd with PHP cells needs no Quarto extension and no configuration beyond naming the kernel. Quarto’s Jupyter engine starts the kernel, runs the cells in order, and puts each cell’s output into the document. This page was checked with Quarto 1.10.18, the version CI tests.

Quarto runs its Jupyter engine in a Python interpreter of its own choosing, which needs Jupyter in it. juphyter doctor reports whether it has, and Python interpreters and editors covers how to choose it.

---
title: "Highway mileage"
jupyter: php
---

A cell is a fenced block whose info string is {php}, holding PHP without an opening tag:

```{php}
$mpg = \Phibble\DataFrame::fromCsv('mpg.csv');
$mpg;
```

quarto render report.qmd writes report.html beside the document. --to chooses another format, such as --to typst for a PDF.

Naming the kernel

jupyter: <name> is the form to use. Quarto resolves the name against the installed kernelspecs, so it is the --name that juphyter install was given.

---
jupyter: php85
---

Jupyter also writes a fuller block into a notebook, and Quarto accepts it in a .qmd. For juphyter it needs one field more than the three Jupyter writes:

---
jupyter:
  kernelspec:
    name: php85
    language: php
    display_name: "PHP 8.5"
    path: /Users/you/Library/Jupyter/kernels/php85
---

path is the directory holding kernel.json, shown here for macOS. Installation lists the directory on Linux. Quarto’s jupyter engine reads it when it looks beside a kernelspec for a quarto_setup_cell, which it does for any language it has no built-in support for, and jupyter: <name> is the only form that fills the field in by itself. Without path the render fails with KeyError: 'path'. Naming the kernel rather than describing it avoids the whole question.

Cell options

Quarto’s cell options are comments in the cell’s own language, written with a | after the comment marker. Quarto has no php entry in its comment-character table and falls back to #, which PHP reads as a line comment, so #| is a Quarto cell option and a PHP comment at once.

The options that matter most in a PHP document:

  • #| echo: false runs the cell and keeps its source out of the page.
  • #| eval: false shows the source and does not run it.
  • #| output: false runs the cell and shows no output.
  • #| error: true lets the render continue past a cell that throws, and keeps the traceback in the output.
  • #| fig-cap: "..." captions the cell’s figure.
  • #| label: fig-mileage gives the figure a cross-reference.

A captioned plot:

Plot::of(Datasets::mpg())->aes(x: 'cty', y: 'hwy')->point(opacity: 0.5);

City against highway mileage.

PHP cells come out syntax-highlighted. Pandoc’s PHP syntax definition expects a <?php opening tag, which a notebook cell has no place for, and it highlights the cell regardless.

Every render prints Unknown message type: 'comm_open' once, from ipykernel rather than from Quarto. The kernel installs no comm manager, which is the same absence that leaves ipywidgets unsupported, and the message has no effect on what is rendered.

What each format carries

A phiz plot publishes four representations and a phibble data frame publishes two. Each output format takes one of them. For HTML Quarto prefers text/html, for LaTeX and Typst it prefers image/svg+xml, and otherwise it falls back to text/plain.

Format A phiz plot A phibble data frame
html the interactive chart, drawn by Vega-Lite in the browser the HTML table, with the dimensions and the type row
typst, pdf the static SVG, placed as a numbered figure a typeset table; the dimension note and the truncation footer are dropped
ipynb the HTML embed nothing: see below

A PDF figure comes from the static SVG, which phiz produces by running the vl-convert binary. Install it from vl-convert’s releases and put it on PATH, or point PHIZ_VL_CONVERT at it. Without it a plot has no SVG and a PDF has no figure.

quarto render --to ipynb is a conversion of the rendered document rather than the notebook that was executed. Quarto keeps one representation per output and reaches ipynb through Pandoc’s Markdown reader. An HTML <table> does not survive that trip, so the output is dropped, and error outputs are dropped as well. This is Quarto’s conversion rather than anything about the kernel, and a stock python3 kernel loses an IPython.display.HTML('<table>...') output in exactly the same way. Render to HTML or to PDF when a document’s tables matter.

PDF output

Quarto produces a PDF through LaTeX or through Typst, and Typst is the easier of the two here. Typst ships inside Quarto and places an SVG as it is:

quarto render report.qmd --to typst

The LaTeX route needs one more tool. For LaTeX output Quarto also picks the SVG, and then has to convert it to a PDF, which it does with rsvg-convert from librsvg. Without that binary quarto render --to pdf stops with Could not convert a SVG to a PDF for output. Install librsvg, or render through Typst.

The kernel daemon

Quarto can keep a kernel alive between renders, which saves starting PHP and loading the project’s autoloader again. Two conditions have to hold, and for a PHP kernel today the second one does not:

  1. The daemon has to be asked for. Quarto turns it on by default only in an interactive session outside CI, and --execute-daemon <seconds> or execute: daemon: <seconds> asks for it explicitly.
  2. Quarto only keeps a kernel whose language it can write a setup or a cleanup cell for. It looks for lang/<language> among its own resources and then for a quarto_setup_cell or quarto_cleanup_cell file beside the kernelspec. It ships neither for PHP, and juphyter install writes neither, so the server exits after one render and the next render starts a new kernel.

The practical effect is that each render of a PHP document gets a fresh session, and nothing of a previous render is in scope. Setting

execute:
  daemon: false

makes that explicit, and is the setting to reach for if a future release starts daemonizing PHP kernels and a document turns out to depend on a fresh session. A document that declares a class or a function is such a document: PHP cannot redeclare either, so a second render against a kept-alive kernel would fail on the cell that declares it.

The engine is ready for the daemon whenever Quarto starts using it. It has no read timeout on its end of the socket, so it waits between requests for as long as the front end leaves it. A kernel made daemonizable on purpose and then left idle for three minutes between two renders answered the second one from the same session. When a kept-alive kernel does end, it takes the PHP engine and the watchdog process that guards it with it, leaving nothing running.

Freeze

freeze works unchanged, because it is Quarto’s own cache of a cell’s output and the kernel is not involved in it.

execute:
  freeze: auto

With freeze: auto a document is executed again only when its source changes. Quarto stores the output in _freeze/, and a project that commits that directory can be rendered on a machine without PHP or the packages the cells use.

Quarto looks up a document’s kernel by name before it reads the frozen output, so a kernel with that name has to be registered on the rendering machine even when no cell runs. Contributing shows how this repository does that for its own site.

Projects

In a Quarto project the kernel is named per document, in each document’s front matter. A project with more than three documents to render turns the daemon off unless --execute-daemon was passed on the command line, which for PHP changes nothing.

Quarto runs the kernel with the document’s own directory as its working directory, and the engine finds the project to load by walking up from there looking for vendor/autoload.php. A document in a subdirectory of a Composer project therefore sees that project. A document outside any Composer project sees none, and JUPHYTER_AUTOLOAD or an autoload entry in a juphyter.json beside the document names one outright.

From phpstats/juphyter@57dda5a.