Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/getting-started/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Docker
---

Run PHP Debugger in a container using an image with the debugger already compiled
into the interpreter, with nothing to install.

:::note
This page is a work in progress — full documentation is coming soon.
:::

In the meantime, the [installer](./installation.mdx) covers installing directly
onto your machine.
12 changes: 12 additions & 0 deletions docs/getting-started/install-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: More install options
---

Ways to install PHP Debugger beyond the installer — prebuilt binaries, package
managers, and building from source.

:::note
This page is a work in progress — full documentation is coming soon.
:::

For the recommended route, see [Installation](./installation.mdx).
34 changes: 0 additions & 34 deletions docs/getting-started/installation.md

This file was deleted.

106 changes: 106 additions & 0 deletions docs/getting-started/installation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: Installation
---

import InstallCommand from '@site/src/components/InstallCommand';

The quickest way to get PHP Debugger onto your machine is the installer, a small
command-line tool that fetches the right build for your system and puts it in
place. Installing it is a single command.

## 1. Get the installer

<InstallCommand />

The script downloads the latest release and drops the `php-debugger` binary into
the current directory. Set `INSTALL_DIR` if you would rather it landed somewhere
else.

:::tip[Prefer Docker?]

Use a container image instead — see [Docker](./docker.md).

:::

## 2. Install the debugger

The installer offers two ways to get a working debugger. Run it with no arguments
for the default:

```bash
php-debugger install
```

That installs a **self-contained PHP interpreter** with the debugger compiled
straight into it, and makes it the active `php` on your PATH. Nothing else on your
system needs to change.

If you would rather keep the PHP you already have, install **only the extension**
into it:

```bash
php-debugger install --extension-only
```

The two are mutually exclusive, and the installer knows it: installing the
interpreter removes any extension you had installed, so you never end up running
both at once.

A few flags are worth knowing:

| Flag | What it does |
| --- | --- |
| `-u, --user` | Install into a per-user directory, so no `sudo` is needed. The default is system-wide. |
| `-p, --php <x.y>` | Pick the PHP version to install. Defaults to the latest, and applies to the interpreter only. |
| `-e, --extension-only` | Install just the extension into your current PHP. |
| `-z, --zts` | Install a thread-safe build instead of the default non-thread-safe one. |
| `-y, --yes` | Answer yes to every prompt, for unattended or CI use. |

If the directory it installs into is not on your `PATH`, the installer prints the
exact line you need to add.

## Changing your mind

Nothing here is a one-way door. Whether you installed the interpreter or just the
extension, whatever it replaced is backed up first — the PHP you already had, or
the ini configuration it changed. To put things back as they were:

```bash
php-debugger uninstall
```

That removes whatever was installed and restores your previous setup from the
backup — the interpreter it replaced, or the ini configuration it changed. You do
not have to tell it which kind you installed; it works that out for itself.

The installer also tries hard not to leave you with a broken `php` in the first
place. It runs the downloaded interpreter before changing anything, so a build
that cannot run on your system is never installed. After activating it, it checks
that `php` still runs and reports the debugger module, and rolls everything back to
the previous working state if either check fails.

## Staying up to date

To move to the latest release:

```bash
php-debugger update
```

It takes no arguments. Whether you installed the interpreter or just the
extension, it detects what is there and reinstalls the same thing against the
latest version.

:::info[Prefer to install some other way?]

See [More install options](./install-options.md).

:::

## Next steps

With the debugger installed, point your editor at it and set your first
breakpoint:

- [Quick Start](./quick-start.md)
- [Configuration](./configuration.md)
2 changes: 1 addition & 1 deletion docs/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ because when you are not using it you can barely tell it is there.

Ready to get started? Install PHP Debugger and try the quick start guide:

- [Installation Guide](./installation.md)
- [Installation Guide](./installation.mdx)
- [Quick Start](./quick-start.md)
2 changes: 1 addition & 1 deletion docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Quick Start
---

Get up and running with PHP Debugger in a few minutes. This guide assumes you have already [installed](./installation.md) the extension.
Get up and running with PHP Debugger in a few minutes. This guide assumes you have already [installed](./installation.mdx) the extension.

## 1. Enable the debugger

Expand Down
2 changes: 1 addition & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const config = {
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['php', 'ini', 'bash'],
additionalLanguages: ['php', 'ini', 'bash', 'powershell'],
},
}),
};
Expand Down
2 changes: 2 additions & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const sidebars = {
items: [
'getting-started/introduction',
'getting-started/installation',
'getting-started/docker',
'getting-started/install-options',
'getting-started/quick-start',
'getting-started/configuration',
],
Expand Down
150 changes: 150 additions & 0 deletions src/components/InstallCommand/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {useEffect, useRef, useState} from 'react';
import clsx from 'clsx';
import CodeBlock from '@theme/CodeBlock';
import styles from './styles.module.css';

/* macOS and Linux run the same script -- it detects the OS and architecture
itself -- so both tabs deliberately show the same command. */
const UNIX_COMMAND =
'curl -fsSL https://github.com/php-debugger/installer/releases/latest/download/install.sh | sh';
const WINDOWS_COMMAND =
'powershell -c "irm https://github.com/php-debugger/installer/releases/latest/download/install.ps1 | iex"';

const iconProps = {
width: 15,
height: 15,
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
'aria-hidden': true,
};

const CopyIcon = () => (
<svg {...iconProps}>
<rect x="9" y="9" width="11" height="11" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
);

const CheckIcon = () => (
<svg {...iconProps}>
<path d="M20 6 9 17l-5-5" />
</svg>
);

const PLATFORMS = [
{id: 'macos', label: 'macOS', language: 'bash', command: UNIX_COMMAND},
{id: 'linux', label: 'Linux', language: 'bash', command: UNIX_COMMAND},
{id: 'windows', label: 'Windows', language: 'powershell', command: WINDOWS_COMMAND},
];

function detectPlatform() {
const ua = navigator.userAgent;
if (/Windows/i.test(ua)) {
return 'windows';
}
if (/Mac OS X|Macintosh/i.test(ua)) {
return 'macos';
}
/* Android reports Linux too, and the command is the same either way. */
if (/Linux|Android|X11/i.test(ua)) {
return 'linux';
}
return null;
}

export default function InstallCommand() {
/* The page is prerendered without knowing the visitor's OS, so start on macOS
and correct it after mounting. Detecting in an effect rather than during
render keeps the first client render identical to the server's, which is
what hydration compares. A visitor who picks a tab keeps their choice --
the effect only runs on mount. */
const [platform, setPlatform] = useState('macos');

useEffect(() => {
const detected = detectPlatform();
if (detected) {
setPlatform(detected);
}
}, []);

const active = PLATFORMS.find((p) => p.id === platform) ?? PLATFORMS[0];
const [copied, setCopied] = useState(false);
const resetTimer = useRef(null);

useEffect(() => () => clearTimeout(resetTimer.current), []);

/* Fallback for when the async Clipboard API is unavailable or refused: it needs
a secure context, and browsers decline it when the document is not focused.
A throwaway textarea and execCommand works in those cases -- it is what
Docusaurus's own copy button relies on. */
function copyViaTextarea(text) {
const field = document.createElement('textarea');
field.value = text;
field.setAttribute('readonly', '');
field.style.position = 'fixed';
field.style.opacity = '0';
document.body.appendChild(field);
field.select();
let ok = false;
try {
ok = document.execCommand('copy');
} catch {
ok = false;
}
document.body.removeChild(field);
return ok;
}

async function copy(event) {
event.currentTarget.blur();
let ok = false;
try {
await navigator.clipboard.writeText(active.command);
ok = true;
} catch {
ok = copyViaTextarea(active.command);
}
/* Only confirm a copy that actually happened. If both routes fail the label
stays put rather than claiming something untrue -- the whole command is on
screen and can still be selected by hand. */
if (!ok) {
return;
}
setCopied(true);
clearTimeout(resetTimer.current);
resetTimer.current = setTimeout(() => setCopied(false), 2000);
}

return (
<div className={styles.wrapper}>
<div className={styles.bar}>
<div className={styles.tabs} role="tablist" aria-label="Operating system">
{PLATFORMS.map((p) => (
<button
key={p.id}
type="button"
role="tab"
aria-selected={p.id === active.id}
className={clsx(styles.tab, p.id === active.id && styles.tabActive)}
onClick={() => setPlatform(p.id)}>
{p.label}
</button>
))}
</div>
<button
type="button"
className={clsx(styles.copy, copied && styles.copied)}
onClick={copy}
aria-label={`Copy the ${active.label} install command`}>
{copied ? <CheckIcon /> : <CopyIcon />}
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<CodeBlock language={active.language}>{active.command}</CodeBlock>
</div>
);
}
Loading
Loading