Tip
Prefer the tyhp/core catalog ($name->toUpper(), $nums->mapped(...)) when a method exists. Write a small user extension when you need a project-specific helper, and import it with use extension in each file that needs it.
Tier 0 · Story 03Complete
Tyhp lets you call methods on scalar values (string, int, float, bool, array) and on \Closure using object syntax. The compiler rewrites $value->method($args) to a PHP expression or a static call. There is zero runtime overhead — no wrapper objects are created.
Tier 2 · Story 21Complete
The tyhp/core package ships a built-in catalog of those methods (length, contains, toUpper, mapped, …) and activates them for the whole compilation with global use extension \Tyhp\StringExtensions (and the matching ArrayExtensions, IntExtensions, FloatExtensions, BoolExtensions, ClosureExtensions). The catalog is ordinary Tyhp extension source inside tyhp/core; see tyhp/core for the full method list. You do not write use extension in each file.
<?tyhp
string $name = "Hello, World!";
int $len = $name->length();
bool $has = $name->contains("World");
string $upper = $name->toUpper();
string $trimmed = " spaces "->trim();
array<int, int> $nums = [3, 1, 4];
array<int, int> $evens = $nums->filtered(fn(int $v): bool => $v % 2 === 0);
array<int, string> $labels = $nums->mapped(fn(int $v): string => (string)$v);
int $n = (42)->abs();
float $rounded = (3.14159)->round(2);
Compiled PHP (excerpt):
$len = \Tyhp\StringHelper::strlen($name);
$has = \str_contains($name, "World");
$trimmed = \trim(" spaces ", " \t\n\r\0\x0B");
$evens = \array_filter($nums, fn(int $v): bool => ($v % 2) === 0, 0);
$labels = \array_map(fn(int $v): string => (string) $v, $nums);
$n = \abs(42);
$rounded = \round(3.14159, 2, \PHP_ROUND_HALF_UP);
Integer and float literals must be parenthesised before -> ((42)->abs(), (-1.5)->abs(), (0x1F)->toHex()); string and array literals can be used directly ("a,b"->split(","), [1, 2, 3]->count()).
byte prefix for bytesString methods that measure or slice characters prefer the multibyte (\mb_*) APIs when ext-mbstring is loaded: length() is \mb_strlen, toUpper() is \mb_strtoupper, substring() is \mb_substr, and so on. Those calls go through \Tyhp\StringHelper, which caches the extension check and falls back to the matching byte API (\strlen, \strtoupper, \substr, …) when mbstring is not installed.
Byte-oriented variants use a byte prefix and the corresponding byte API: byteLength() is \strlen, byteReverse() is \strrev.
use only when adaptingBecause tyhp/core already issued global use extension, a file-level use extension \Tyhp\StringExtensions; with no adaptations is redundant and warns (TYHP4169). Use a local use only to adapt the catalog in that file (hide, method as, insteadof):
<?tyhp
use extension \Tyhp\StringExtensions {
StringExtensions::toLower hide;
}
function lengthOf(string $name): int {
return $name->length();
}
Inside the file, toLower is no longer available as a method; every other StringExtensions method still is.
See Use Statements.
Declare an extension with the target on the header (extends string) or in a nested extends group. $this is that target and is not a caller parameter. Same-file extensions are in scope without an import. Extensions from another file or namespace need use extension unless a loaded tyhpdef used global use extension for that name.
<?tyhp
extension StringHelpers extends string {
function toCamelCase(): string {
string $parts = \str_replace('_', ' ', $this);
return \lcfirst(\str_replace(' ', '', \ucwords($parts)));
}
function trimLower(): string {
return \strtolower(\trim($this));
}
}
string $name = 'hello_world';
string $camel = $name->toCamelCase();
string $tidy = $name->trimLower();
Compiles to:
<?php
declare(strict_types=1);
$name = 'hello_world';
$camel = \StringHelpers::toCamelCase($name);
$tidy = \strtolower(\trim($name));
toCamelCase has several statements, so it is emitted as a static method on StringHelpers (with $this passed as the first argument) and call sites call it. trimLower is a single return, so its expression is inlined at each call site.
When the extension lives in another file or namespace, import it with use extension:
<?tyhp
use extension App\Extensions\StringHelpers;
string $name = 'hello_world';
string $camel = $name->toCamelCase();
Without the import, a call to the extension's method is TYHP4101. See Use Statements for adaptation syntax (as / hide) on use extension.
If your extensions return the extended type (or another type that also has extensions in scope), you can chain calls. Each call is rewritten independently.
<?tyhp
extension StringHelpers extends string {
function trimmed(): string {
return \trim($this);
}
function lower(): string {
return \strtolower($this);
}
function dashed(): string {
string $spaced = \str_replace(' ', '-', $this);
return $spaced;
}
}
string $input = " Hello World ";
$result = $input->trimmed()->lower()->dashed();
<?php
$input = " Hello World ";
$result = \StringHelpers::dashed(\strtolower(\trim($input)));
trimmed and lower are single-return members, so they are inlined; dashed has two statements, so it stays a static call.
The built-in catalog chains the same way: " Hello "->trim()->toLower().
Extension methods can be called on literals. Wrap integer and float literals in parentheses.
<?tyhp
$char = (65)->toHex();
$parts = "a,b,c"->split(",");
Prefer the tyhp/core catalog ($name->toUpper(), $nums->mapped(...)) when a method exists. Write a small user extension when you need a project-specific helper, and import it with use extension in each file that needs it.
Put the scalar on the extension (extends string, extends array, …) so call sites read as instance methods while emit stays a static call or a spliced expression.
Don't add use extension \Tyhp\StringExtensions; with no adaptations. The catalog is already globally imported; that local use warns TYHP4169.
Don't expect actual object instances — these are compile-time rewrites. Calling a method that is not an in-scope extension produces a compile error.
Don't import an extension class with a plain use and expect methods to attach. Use use extension to activate extension methods on the target type (same-file extensions and tyhp/core globals are already in scope).
Don't try to assign scalar methods to variables — $fn = $name->trim is not valid. These are method calls, not callable properties.