Core
object
%cookies
/phlo/resources/cookies.phlo
method
%cookies -> controller
line 9
Deze controller haalt de huidige staat van cookies op en wijst deze toe aan de objData-eigenschap.
this->objData = $_COOKIEprop
%cookies -> lifetimeDays
line 11
Stelt de levensduur van cookies in dagen in.
180method
%cookies -> objSet ($key, $value, array $options = [])
line 13
Stelt een cookie in met de opgegeven sleutel en waarde, samen met optionele parameters voor vervaldatum, pad, beveiliging en SameSite-attributen.
$this->objData[$key] = $value
$_COOKIE[$key] = $value
$defaults = ['expires' => time() + $this->lifetimeDays * 86400, 'path' => slash, 'secure' => %req->secure, 'httponly' => true, 'samesite' => 'Lax']
setcookie($key, $value, array_merge($defaults, $options))
return truemethod
%cookies -> __unset ($key)
line 21
Verwijdert een cookie door deze uit de lokale objectgegevens en de globale $_COOKIE-array te verwijderen, en stelt de vervaldatum in op het verleden.
unset($this->objData[$key], $_COOKIE[$key])
$options = ['expires' => time() - 86400, 'path' => slash, 'secure' => %req->secure, 'httponly' => true, 'samesite' => 'Lax']
setcookie($key, void, $options)object
%lang
/phlo/resources/lang.phlo
function
function nl ($text, ...$args)
line 11
Vertaal de gegeven tekst naar het Nederlands met de opgegeven argumenten voor opmaak.
%lang->translation('nl', $text, ...$args)function
function en ($text, ...$args)
line 12
Deze functie haalt een vertaling op voor de opgegeven tekst in het Engels, en kan optioneel worden opgemaakt met extra argumenten.
%lang->translation('en', $text, ...$args)static
lang :: asyncBatch ($from, $to, $json)
line 14
Voert een batchvertaling asynchroon uit, decodeert JSON-invoer en slaat de vertalingen op als deze succesvol zijn.
%app->lang = $to
$texts = json_decode($json, true)
$translations = $this->translateBatch($from, $to, $texts)
if ($translations) $this->save($to, $translations)view
%lang -> view
line 21
Deze functie haalt de huidige taalinstelling van de applicatie op, waardoor lokalisatie van views mogelijk is.
%app->langprop
%lang -> model
line 23
Deze functie haalt het model op dat is gekoppeld aan de opgegeven taalidentificator.
'gpt-4o-mini'prop
%lang -> instructions
line 24
Definieert een set instructies voor de Phlo programmeertaal.
voidstatic
lang :: fileCache
line 25
lang::$fileCache is een statische eigenschap die gecachete taaldocumenten opslaat voor efficiënte toegang tijdens runtime.
[]method
%lang -> file ($lang)
line 27
Haal het configuratiebestand op voor de opgegeven taal, met het formaat 'langs.$lang.ini'.
langs.$lang.'.ini'method
%lang -> escape ($value)
line 29
Escaped speciale tekens in een string voor veilige uitvoer in HTML, vervangend backslashes, dubbele aanhalingstekens en regeleinden met hun respectieve escape-sequenties.
strtr((string)$value, [bs => bs.bs, dq => bs.dq, lf => '\n'])method
%lang -> unescape ($value)
line 30
Deze functie ontsnapt een gegeven string door escape-sequenties te vervangen door hun overeenkomstige karakters.
strtr(strtr($value, [bs.bs => "\x01", bs.dq => dq, '\n' => lf]), ["\x01" => bs])method
%lang -> lineValue ($line, $eq)
line 32
Extraheert en verwerkt een lijnwaarde uit een gegeven string, verwijdert omringende aanhalingstekens indien aanwezig en ontsnapt speciale tekens.
$value = rtrim(substr($line, $eq + 3), cr.lf)
if (strlen($value) > 1 && $value[0] === dq && substr($value, -1) === dq) $value = substr($value, 1, -1)
return $this->unescape($value)method
%lang -> readAll ($file)
line 38
Leest alle sleutel-waarde paren uit een opgegeven bestand en retourneert deze als een associatieve array. Als het bestand niet bestaat of geen geldig bestand is, retourneert het een lege array.
$items = []
if (!is_file($file)) return $items
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] AS $line){
$eq = strpos($line, ' = ')
if ($eq === false) continue
$items[substr($line, 0, $eq)] = $this->lineValue($line, $eq)
}
return $itemsmethod
%lang -> search ($file, $hash)
line 49
Zoekt naar een specifieke hash in een bestand en retourneert de bijbehorende waarde als deze is gevonden.
$size = (int)@filesize($file)
if (!$size) return null
$h = @fopen($file, 'rb')
if (!$h) return null
$lo = 0
$hi = $size
while ($hi - $lo > 4096){
$mid = intdiv($lo + $hi, 2)
fseek($h, $mid)
fgets($h)
$pos = ftell($h)
if ($pos >= $hi){
$hi = $mid
continue
}
$line = (string)fgets($h)
$eq = strpos($line, ' = ')
if ($eq === false){
$hi = $mid
continue
}
$cmp = strcmp(substr($line, 0, $eq), $hash)
if ($cmp < 0) $lo = ftell($h)
elseif ($cmp > 0) $hi = $pos
else {
fclose($h)
return $this->lineValue($line, $eq)
}
}
fseek($h, $lo)
$value = null
while (ftell($h) < $hi && ($line = fgets($h)) !== false){
$eq = strpos($line, ' = ')
if ($eq === false) continue
$cmp = strcmp(substr($line, 0, $eq), $hash)
if ($cmp > 0) break
if ($cmp === 0){
$value = $this->lineValue($line, $eq)
break
}
}
fclose($h)
return $valuemethod
%lang -> lookup ($hash)
line 95
Zoekt een waarde in de taalbestanden cache op basis van de opgegeven hash en werkt de cache bij als het bestand is gewijzigd.
$file = $this->file(%app->lang)
$mtime = (int)@filemtime($file)
$cache =& static::$fileCache[$file]
if (!$cache || $cache['mtime'] !== $mtime) $cache = ['mtime' => $mtime, 'items' => []]
if (array_key_exists($hash, $cache['items'])) return $cache['items'][$hash]
$value = $this->search($file, $hash)
if ($value === null && $mtime) $value = $this->readAll($file)[$hash] ?? null
return $cache['items'][$hash] = $valuemethod
%lang -> save ($lang, $pairs)
line 106
Slaat een set van sleutel-waardeparen op in een taaldocument, waarbij de sleutels worden gesorteerd en de bestandsrechten correct worden ingesteld.
$file = $this->file($lang)
$items = $this->readAll($file)
foreach ($pairs AS $hash => $value) $items[$hash] = $value
ksort($items, SORT_STRING)
$out = void
foreach ($items AS $hash => $value) $out .= $hash.' = '.dq.$this->escape($value).dq.lf
$tmp = $file.'.'.getmypid().'.tmp'
file_put_contents($tmp, $out, LOCK_EX)
@chmod($tmp, 0664)
rename($tmp, $file)
unset(static::$fileCache[$file])method
%lang -> transContext
line 120
Haal de context op van de app-auteur met betrekking tot het doel en het domein, indien beschikbaar; anders retourneert het void.
($instr = trim($this->instructions ?? void)) !== void ? lf.'Context from the app author about purpose and domain: '.$instr : voidprop
%lang -> browser
line 122
Haal de voorkeurstaal uit de 'Accept-Language' HTTP-header, en retourneer de eerste overeenkomende taalcodes uit de ondersteunde talen van de applicatie.
last($langs = array_filter(explode(comma, %req->acceptLanguage), fn($lang) => isset(%app->langs[substr($lang, 0, 2)])), $langs ? substr(current($langs), 0, 2) : null)method
%lang -> cookie
line 123
Haal de taalvoorkeur uit cookies en controleer of het een geldige optie is in de beschikbare talen van de applicatie, waarbij de taal wordt geretourneerd als deze geldig is of null anders.
($lang = %cookies->lang) && %app->langs[$lang] ? $lang : nullmethod
%lang -> detect ($text, $fallback = 'en')
line 124
Detecteert de taal van de gegeven tekst en retourneert de ISO 639-1 code. Als de detectie mislukt, retourneert het een opgegeven fallback-taalcode, standaard 'en'.
$res = %AI->chat (
model: $this->model,
system: 'Analyse which language this text is in and return only the ISO 639-1 code of the language, no other data!',
user: $text.lf.lf.'The ISO 639-1 code of the language is: ',
temperature: 0,
)->answer
return strlen($res) === 2 ? strtolower($res) : $fallbackmethod
%lang -> hash ($from, $text)
line 133
Genereert een hash op basis van de opgegeven tekst en een voorvoegsel van de opgegeven taal.
$from.($short = substr(implode(regex_all('/[A-Za-z0-9]+/', ucwords($text))[0]), 0, 8)).substr(md5($text), 0, 10 - strlen($short))method
%lang -> translation ($from, $text, ...$args)
line 134
Vertaling van de gegeven tekst van een opgegeven taal naar de huidige taal van de applicatie, waarbij ontbrekende vertalingen asynchroon worden afgehandeld.
if ($from === %app->lang) $translation = strtr($text, ['\n' => lf])
else {
$translation = []
$missing = []
foreach (explode(lf, $text) AS $line){
if (trim($line)){
$hash = $this->hash($from, $line)
$item = $this->lookup($hash)
if ($item === null) [$missing[$hash] = $item = $line, debug(%app->lang.': '.(strlen($line) > 20 ? substr($line, 0, 18).'...' : $line))]
}
else $item = void
$translation[] = $item
}
if ($missing) phlo_async('lang::asyncBatch', $from, %app->lang, json_encode($missing))
$translation = implode(lf, $translation)
}
return $args ? sprintf($translation, ...$args) : $translationmethod
%lang -> translate ($from, $to, $text)
line 153
Vertaal een gegeven tekst van de ene ISO 639-1 taal naar de andere met behulp van AI, terwijl markdown-opmaak en hoofdletters behouden blijven.
if ($from === $to) return $text
return %AI->chat (
model: $this->model,
system: "You will be provided with a word, sentence or (markdown) text in ISO 639-1 language $from, and your task is to translate this string into ISO 639-1 language $to. Respect markdown, missing interpunction and specific use of capitals. Give only the translation.".$this->transContext(),
user: $text,
temperature: 0,
)->answermethod
%lang -> translateBatch ($from, $to, $texts)
line 162
Vertaal een batch tekst van de ene naar de andere taal met behulp van AI, en geef de vertalingen in genummerd formaat terug.
if ($from === $to) return $texts
$hashes = array_keys($texts)
$numbered = implode(lf, array_map(fn($i, $t) => ($i + 1).'. '.$t, array_keys($values = array_values($texts)), $values))
$answer = %AI->chat (
model: $this->model,
system: "You will be provided with numbered lines in ISO 639-1 language $from. Translate each line into ISO 639-1 language $to. Return only the numbered translations in the same format. Respect markdown, missing interpunction and specific use of capitals.".$this->transContext(),
user: $numbered,
temperature: 0,
)->answer
$result = []
foreach (explode(lf, trim($answer)) AS $line){
if (preg_match('/^(\d+)\.\s*(.+)/', $line, $m))
$result[$hashes[(int)$m[1] - 1]] = $m[2]
}
return $resultobject
%payload
/phlo/resources/payload.phlo
method
%payload -> controller
line 10
contentType = %req->contentType
if (in_array(phlo('req')->method, ['POST', 'PUT', 'PATCH', 'QUERY']) && str_starts_with($contentType, 'application/json')){
$data = json_read('php://input')
return $this->objData = is_object($data) ? get_object_vars($data) : (is_array($data) ? $data : [])
}
if ($_POST) $this->objImport(...$_POST)
elseif (in_array(phlo('req')->method, ['PUT', 'PATCH', 'QUERY']) && str_starts_with($contentType, 'application/x-www-form-urlencoded')){
$body = file_get_contents('php://input')
$data = []
parse_str($body, $data)
if ($data) $this->objImport(...$data)
}
elseif (in_array(phlo('req')->method, ['PUT', 'PATCH', 'QUERY']) && str_starts_with($contentType, 'multipart/form-data')){
$match = regex('/boundary="?([^";]+)"?/', $contentType)
if (!$match) return
$boundary = '--'.$match[1]
$arrays = []
$raw = file_get_contents('php://input')
foreach (explode($boundary, $raw) AS $part){
if (!trim($part) || $part === '--' || !str_contains($part, nl.nl)) continue
$headers = []
[$rawHeaders, $body] = explode(nl.nl, $part, 2)
foreach (explode(nl, trim($rawHeaders)) AS $header){
if (str_contains($header, colon)){
[$key, $value] = explode(colon, $header, 2)
$headers[strtolower(trim($key))] = trim($value)
}
}
if (!isset($headers['content-disposition'])) continue
if (!preg_match('/name="([^"]+)"/', $headers['content-disposition'], $match)) continue
$name = $match[1]
$body = rtrim($body, nl)
if ($body === void) $body = null
$base = $name
$keys = []
$hasEmptyIndex = false
if (preg_match('/^([^\[]+)((?:\[[^\]]*\])*)$/', $name, $m)){
$base = $m[1]
$brackets = $m[2]
if ($brackets){
preg_match_all('/\[([^\]]*)\]/', $brackets, $mm)
$keys = $mm[1]
$hasEmptyIndex = in_array(void, $keys, true)
}
}
if ($hasEmptyIndex) $arrays[] = $base
$assign = function($value) use ($base, $keys){
if ($keys){
if (!isset($this->objData[$base]) || !is_array($this->objData[$base])) $this->objData[$base] = []
$ref =& $this->objData[$base]
$count = count($keys)
foreach ($keys AS $i => $k){
$last = $i === $count - 1
if ($k === void){
if ($last) $ref[] = $value
else {
$ref[] = []
end($ref)
$idx = key($ref)
$ref =& $ref[$idx]
}
}
else {
if ($last) $ref[$k] = $value
else {
if (!isset($ref[$k]) || !is_array($ref[$k])) $ref[$k] = []
$ref =& $ref[$k]
}
}
}
}
else $this->objData[$base] = $value
};
if (preg_match('/filename="([^"]*)"/', $headers['content-disposition'], $f)){
if ($f[1] === void || $body === null){
if (!$hasEmptyIndex) $assign(null)
continue
}
$filename = $f[1]
$file = %file(tempnam(sys_get_temp_dir(), 'phlo'), $filename, $body)
$assign($file)
}
else $assign($body)
}
foreach ($this->objData AS $key => $val){
if (str_ends_with($key, '[]')){
unset($this->objData[$key])
$this->objData[substr($key, 0, -2)] = is_array($val) ? array_values(array_filter($val, fn($v) => $v !== null)) : [$val]
}
elseif (!is_array($val) && substr($key, -2) === '[]') $this->objData[$key] = [$val]
}
foreach (array_unique($arrays) AS $key) if (!isset($this->objData[$key])) $this->objData[$key] = []
}
if ($_FILES) $this->objImport(...loop($_FILES, fn($f) => is_array($f['name']) ? loop(array_keys($f['name']), fn($i) => $f['error'][$i] ? null : %file($f['tmp_name'][$i], $f['name'][$i], mime: $f['type'][$i], size: $f['size'][$i])) : ($f['error'] ? null : %file($f['tmp_name'], $f['name'], mime: $f['type'], size: $f['size']))))object
%seo
/phlo/resources/seo.phlo
route
route GET sitemap.xml
line 10
Deze route genereert een sitemap voor SEO-doeleinden wanneer deze via een GET-verzoek wordt benaderd.
output($this)route
route GET robots.txt
line 12
Definieert een route die reageert op GET-verzoeken door de inhoud van het robots-bestand in platte tekstformaat met UTF-8-charset uit te geven.
output($this->robots(), type: 'text/plain; charset=utf-8')method
%seo -> robots
line 14
Genereert de inhoud van een robots.txt-bestand op basis van de instellingen van de applicatie, waarbij wordt gespecificeerd welke gebruikersagenten zijn toegestaan of geweerd bij het toegang krijgen tot bepaalde paden, en voegt een link naar de sitemap toe.
if (!(defined('indexable') && indexable)) return 'User-agent: *'.lf.'Disallow: /'.lf
$lines = ['User-agent: *', 'Allow: /']
foreach ((array)(%app->robotsDisallow ?? []) AS $path) $lines[] = 'Disallow: '.$path
$lines[] = 'Sitemap: '.%req->base.slash.'sitemap.xml'
return implode(lf, $lines).lfmethod
%seo -> intl ($uri)
line 22
Haal de geïndividualiseerde versie van een URI op uit de slugs van de applicatie, en retourneer de originele URI als er geen vertaling wordt gevonden.
(%app->slugs ?? [])[$uri] ?? $urimethod
%seo -> locale
line 24
Deze functie haalt de locale-code op die overeenkomt met de taalinstelling van de applicatie, met 'en' als standaard als deze niet is ingesteld.
$lang = %app->lang ?? 'en'
$map = ['nl' => 'nl_NL', 'en' => 'en_US', 'de' => 'de_DE', 'fr' => 'fr_FR', 'es' => 'es_ES', 'it' => 'it_IT', 'pt' => 'pt_PT', 'pl' => 'pl_PL', 'ru' => 'ru_RU', 'el' => 'el_GR', 'tr' => 'tr_TR', 'zh' => 'zh_CN']
return $map[$lang] ?? $langprop
%seo -> ogTitle
line 30
Dit haalt de Open Graph-titel op voor SEO-doeleinden met behulp van de title() functie.
title()prop
%seo -> ogDescr
line 31
Haal de beschrijving van de applicatie op, retourneert void als deze niet is ingesteld.
%app->description ?? voidprop
%seo -> ogImage
line 32
Deze expressie haalt de Open Graph-afbeeldings-URL op voor SEO-doeleinden, met 'icon.webp' als standaard als er geen afbeelding is opgegeven.
%req->base.slash.ltrim(%app->image ?? 'icon.webp', slash)prop
%seo -> canonical
line 33
Dit haalt de canonical URL op voor SEO-doeleinden uit het aanvraagobject.
%req->urlprop
%seo -> ogType
line 34
Stelt het Open Graph-type in voor SEO-doeleinden, specifiek op 'website'.
'website'prop
%seo -> siteName
line 35
Deze expressie wijst de waarde van de titel van de app of de id toe aan de variabele $siteName voor SEO-doeleinden.
%app->title ?? idprop
%seo -> twitterCard
line 36
De seo->$twitterCard eigenschap wordt gebruikt om de Twitter Card metadata voor een view te configureren, waardoor een betere integratie met Twitter deelfunctionaliteiten mogelijk is.
falseprop
%seo -> noIndex
line 37
De seo->$noIndex expressie evalueert naar true als zowel %app->noLink als %app->noIndex niet zijn ingesteld, anders retourneert het false.
%app->noLink ?? %app->noIndex ?? falseview
%seo -> view
line 39
Genereert een XML-sitemap voor de applicatie door over de gedefinieerde pagina's te itereren en hun URL's op te nemen.
<?xml version=1.0 encoding="UTF-8"?>
<urlset xmlns=http://www.sitemaps.org/schemas/sitemap/0.9 xmlns:xhtml=http://www.w3.org/1999/xhtml xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation=http://www.sitemaps.org/schemas/sitemap/0.9+http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd>
<foreach %app->pages AS $uri>
{{ $this->page($uri) }}
</foreach>
</urlset>view
%seo -> page ($uri)
line 47
Genereert een SEO-vriendelijke XML-sitemapvermelding voor een view, inclusief gelokaliseerde URL's voor elke taal die door de applicatie wordt ondersteund.
<url>
<loc>%req->base{( $uri ?: slash )}</loc>
<foreach array_keys(%app->langs) AS $lang>
<if $lang === %app->lang>
{{ $this->xlink('x-default', $uri ?: slash) }}
</if>
{{ $this->xlink($lang, $lang === %app->lang ? ($uri ?: slash) : "/$lang".($this->intl($uri) ?: void)) }}
</foreach>
</url>view
%seo -> xlink ($lang, $uri)
line 58
Genereert een alternatieve linkelement voor SEO-doeleinden, waarbij de taal en URL voor de huidige weergave worden gespecificeerd.
<xhtml:link rel=alternate hreflang="$lang" href="%req->base$uri"{{ slash }}>view
%seo -> link ($lang, $uri)
line 59
Genereert een alternatieve link-tag voor SEO-doeleinden, die de taal en de URL van de bron specificeert.
<link rel=alternate hreflang="$lang" href="%req->base$uri">view
%seo -> head
line 61
Genereert SEO-gerelateerde meta-tags voor een view, inclusief Open Graph- en Twitter Card-tags, op basis van de opgegeven eigenschappen zoals beschrijving, titel en afbeelding.
<if $this->noIndex>
<meta name=robots content=noindex,follow>
</if>
<if $this->ogDescr>
<meta name=description content="$this->ogDescr">
</if>
<meta property=og:site_name content="$this->siteName">
<meta property=og:title content="$this->ogTitle">
<meta property=og:description content="$this->ogDescr">
<meta property=og:type content="$this->ogType">
<meta property=og:url content="$this->canonical">
<meta property=og:image content="$this->ogImage">
<meta property=og:locale content="$this->locale">
<if $this->twitterCard>
<meta name=twitter:card content=summary_large_image>
<meta name=twitter:title content="$this->ogTitle">
<meta name=twitter:description content="$this->ogDescr">
<meta name=twitter:image content="$this->ogImage">
</if>
<if !$this->noIndex>
<link rel=canonical href="$this->canonical">
</if>object
%session
/phlo/resources/session.phlo
method
%session -> controller
line 9
Initialiseert de sessie en wijst de sessiegegevens toe aan de objData-eigenschap.
ession_start()
$this->objData = $_SESSIONmethod
%session -> __set ($key, $value)
line 12
Stelt een sessievariabele in met de opgegeven sleutel op de gegeven waarde.
$_SESSION[$key] = $this->objData[$key] = $valuemethod
%session -> __unset ($key)
line 13
Verwijdert de opgegeven sleutel uit de sessiegegevens en de interne objectgegevens.
unset($this->objData[$key], $_SESSION[$key])method
%session -> __isset ($key)
line 14
Controleert of een sessievariabele die door de gegeven sleutel is geïdentificeerd, is ingesteld en niet null is.
isset($this->objData[$key])method
%session -> objRegenerateId ($deleteOld = true)
line 16
Regenerates de sessie-ID voor de huidige sessie, waarbij optioneel de oude sessiegegevens worden verwijderd op basis van de $deleteOld-parameter.
session_regenerate_id($deleteOld)
$this->objData = $_SESSIONobject
%stream
/phlo/resources/stream.phlo
function
function stream ($data = null, string $type = 'application/octet-stream', ?string $name = null):void
line 10
$res = %res
$cli = %req->cli
!$res->streaming && $res->done && error('Output already started, invalid stream()')
if (!$res->streaming){
$res->streaming = true
$res->type = $type
$res->header('Cache-Control', 'no-store')
$res->header('X-Content-Type-Options', 'nosniff')
$res->header('X-Accel-Buffering', 'no')
$name === null || $res->header('Content-Disposition', 'attachment; filename="'.str_replace('"', '', $name).'"')
$res->render()
}
if ($data === null) return
foreach (is_iterable($data) ? $data : [$data] as $part){
print((string)$part)
$cli || [@ob_flush(), flush()]
}view
script
line 30
app.stream = async (path, onData = null, async = false, type = null, data = null) => {
const url = `${location.origin}/${path}`
const headers = {}
const csrf = obj('meta[name="csrf"]')?.content
csrf && (headers['X-CSRF-Token'] = csrf)
async && (headers['X-Requested-With'] = 'phlo')
let body = data
if (body !== null && !(body instanceof FormData) && !(body instanceof Blob)) [body = JSON.stringify(body), headers['Content-Type'] = 'application/json']
phlo.log(`⇣ APP.STREAM ${url}`)
const res = await fetch(url, {method: body === null ? 'GET' : 'POST', credentials: 'same-origin', headers, body})
if (!res.ok) throw new Error(`stream ${url} 🔴 ${res.status}`)
const mime = (res.headers.get('content-type') ?? '').split(';')[0].trim()
const kind = type ?? (mime === 'application/x-ndjson' ? 'ndjson' : mime === 'application/json' ? 'json' : mime === 'text/event-stream' ? 'sse' : mime.startsWith('text/') ? 'text' : 'raw')
const emit = value => onData && onData(value)
if (kind === 'json'){
const value = await res.json()
emit(value)
return value
}
if (kind === 'blob'){
const value = await res.blob()
emit(value)
return value
}
const reader = res.body.getReader()
const decoder = new TextDecoder()
const out = []
const parts = []
let text = ''
let buffer = ''
const line = raw => {
if (!raw) return
const value = JSON.parse(raw)
out.push(value)
emit(value)
}
const frame = raw => {
if (!raw.trim()) return
let event = 'message'
const data = []
raw.split('\n').forEach(l => l.startsWith('event:') ? event = l.slice(6).trim() : l.startsWith('data:') && data.push(l.slice(5).replace(/^ /, '')))
const value = {event, data: data.join('\n')}
out.push(value)
emit(value)
}
while (true){
const {done, value} = await reader.read()
if (done) break
if (kind === 'raw'){
parts.push(value)
emit(value)
continue
}
const piece = decoder.decode(value, {stream: true})
if (kind === 'text'){
text += piece
emit(piece)
continue
}
buffer += piece
if (kind === 'sse'){
const hold = buffer.endsWith('\r') ? '\r' : ''
buffer = buffer.slice(0, buffer.length - hold.length).replace(/\r\n?/g, '\n') + hold
}
const pieces = buffer.split(kind === 'sse' ? '\n\n' : '\n')
buffer = pieces.pop()
pieces.forEach(kind === 'sse' ? frame : line)
}
if (kind === 'raw') return new Blob(parts, {type: mime || 'application/octet-stream'})
const tail = decoder.decode()
if (kind === 'text'){
tail && [text += tail, emit(tail)]
return text
}
buffer += tail
buffer && (kind === 'sse' ? frame(buffer.replace(/\r\n?/g, '\n')) : line(buffer))
return out
}object
%tasks
/phlo/resources/tasks.phlo
static
tasks :: dir
line 9
Toegang tot het pad van de directory voor taken, specifiek verwijzend naar 'tasks/'.
data.'tasks/'static
tasks :: run
line 11
Voert geplande taken uit door hun vervaldatum te controleren en ze te vergrendelen om gelijktijdige uitvoering te voorkomen. Het slaat de uitvoeringsdetails op en markeert de taak als voltooid na uitvoering.
is_dir(static::dir()) || mkdir(static::dir(), 0755, true)
$now = time()
foreach (%app->tasks ?? [] AS $name => $task){
$task = (object)$task
if (!static::due($name, $task, $now)) continue
if (!static::lock($name)) continue
$schedule = array_intersect_key((array)$task, array_flip(['every', 'daily', 'weekly']))
$do = is_string($task->do) ? $task->do : null
static::saveRun($name, $do, $schedule, static::fire($task->do))
static::markRun($name, $now)
static::unlock($name)
}static
tasks :: saveRun ($name, $do, $schedule, $return)
line 26
Slaat de uitvoergegevens op in een JSON-bestand in de opgegeven map, met de opgegeven naam, do, schema en return-waarden.
json_write(static::dir().$name.'.json', arr(do: $do, schedule: $schedule, return: $return))static
tasks :: due ($name, $task, $now)
line 28
Bepaalt of een geplande taak moet worden uitgevoerd op basis van de frequentie-instellingen, zoals 'elke', 'dagelijks' of 'wekelijks'. Het controleert de laatste uitvoeringstijd ten opzichte van de huidige tijd om te beslissen of de taak moet worden uitgevoerd.
$last = static::lastRun($name)
if (isset($task->every)){
$every = preg_match('/^\d/', $task->every) ? $task->every : '1 '.$task->every
$seconds = strtotime("+$every", 0) ?: 0
return $seconds > 0 && ($now - $last) >= $seconds
}
if (isset($task->daily)){
if (date('H:i', $now) !== $task->daily) return false
return $last < strtotime('today 00:00', $now)
}
if (isset($task->weekly)){
if (date('D H:i', $now) !== date('D H:i', strtotime($task->weekly, $now))) return false
return $last < strtotime('monday this week', $now)
}
return falsestatic
tasks :: fire ($do)
line 46
Voert een taak uit die is gedefinieerd door een Closure, een 'Class::method' string of een resource-naam string, en retourneert het resultaat van de uitvoering.
if ($do instanceof \Closure) return $do()
if (is_string($do) && str_contains($do, '::')){
[$class, $method] = explode('::', $do, 2)
return $class::$method()
}
if (is_string($do)) return phlo($do)
error('Task do must be Closure, "Class::method" string, or resource-name string')static
tasks :: lastRun ($name)
line 56
Haal de laatste uitvoeringstijdstempel van een taak op uit een bestand, en retourneer 0 als het bestand niet bestaat.
$file = static::dir().$name.'.last'
return is_file($file) ? (int)file_get_contents($file) : 0static
tasks :: markRun ($name, $ts)
line 61
Schrijft de tijdstempel van de laatste uitvoering van een taak naar een bestand met de naam van de taak in de opgegeven directory, met gebruik van exclusieve vergrendeling om gelijktijdige schrijfbewerkingen te voorkomen.
file_put_contents(static::dir().$name.'.last', (string)$ts, LOCK_EX)static
tasks :: lock ($name)
line 63
Maakt een lockbestand voor een taak aan als het nog niet bestaat of ouder is dan een uur.
$file = static::dir().$name.'.lock'
if (is_file($file) && (time() - filemtime($file)) < 3600) return false
touch($file)
return truestatic
tasks :: unlock ($name)
line 70
Verwijdert het vergrendelingsbestand dat aan een taak is gekoppeld, zodat deze opnieuw kan worden uitgevoerd.
@unlink(static::dir().$name.'.lock')object
%useragent
/phlo/resources/useragent.phlo
prop
%useragent -> source
line 9
Deze expressie haalt de user agent-string op uit het aanvraagobject en retourneert null als deze niet is ingesteld.
%req->userAgent ?: nullprop
%useragent -> os
line 11
Bepaalt het besturingssysteem op basis van de user agent-string door deze te vergelijken met vooraf gedefinieerde patronen.
if (!$this->source) return 'Unknown'
$list = [
'Android' => '/Android/i',
'iPadOS' => '/iPad.*OS/i',
'iOS' => '/iPhone|iPod/i',
'Windows' => '/Windows NT/i',
'macOS' => '/Mac OS X/i',
'ChromeOS' => '/CrOS/i',
'Linux' => '/Linux/i',
]
foreach ($list AS $n => $r) if (preg_match($r, $this->source)) return $n
if (preg_match('/iPad/i',$this->source) && preg_match('/Mac OS X/i',$this->source)) return 'iPadOS'
return 'Unknown'prop
%useragent -> osV
line 27
Haalt de versie van het besturingssysteem uit de user agent-string als deze beschikbaar is, en retourneert deze in een schone indeling.
if (!$this->source) return void
if (preg_match('/(?:Android|OS X|OS|Windows NT)\s*([0-9._]+)/i', $this->source, $m)){
$v = strtr($m[1], [us => dot])
$v = preg_replace('/[^0-9.].*/', void, $v)
$v = preg_replace('/(?:\.0)+$/', void, $v)
return $v
}
return voidprop
%useragent -> osFull
line 38
Geeft de volledige naam van het besturingssysteem terug, samen met de versie als deze beschikbaar is; anders retourneert het 'Onbekend' of alleen de naam van het besturingssysteem.
if (!$this->OS) return 'Unknown'
if ($this->OS === 'Windows') return 'Windows'
$v = $this->osV
if (!$v) return $this->OS
$short = preg_replace('/^(\d+\.\d+).*/','$1',$v)
if (preg_match('/\.0$/',$short)) $short = preg_replace('/\.0$/', void, $short)
return trim($this->OS.space.$short)prop
%useragent -> name
line 48
Bepaalt de naam van de webbrowser op basis van de gebruikersagentstring die in de source is opgegeven. Het controleert verschillende patronen om populaire browsers zoals Chrome, Firefox en Safari te identificeren en retourneert 'Onbekend' als er geen overeenkomst wordt gevonden.
if (!$this->source) return 'Unknown'
if (preg_match('/\bwv\b/',$this->source) || (preg_match('/Version\/\d+\.\d+/',$this->source) && strpos($this->source,'Chrome/')!==false && strpos($this->source,'Safari/')!==false && strpos($this->source,' Mobile ')!==false)) return 'Android WebView'
if (preg_match('/CriOS\/([0-9.]+)/',$this->source)) return 'Chrome'
if (preg_match('/FxiOS\/([0-9.]+)/',$this->source)) return 'Firefox'
$list = [
'Edge' => '/Edg\/([0-9.]+)/',
'Opera' => '/OPR\/([0-9.]+)/',
'Samsung Internet' => '/SamsungBrowser\/([0-9.]+)/i',
'Chrome' => '/Chrome\/([0-9.]+)/',
'Firefox' => '/Firefox\/([0-9.]+)/',
'Safari' => '/Version\/([0-9.]+).*Safari/i',
]
foreach ($list AS $n => $r) if (preg_match($r, $this->source)) return $n
return 'Unknown'prop
%useragent -> version
line 65
Haalt het versienummer uit de user agent-string als deze overeenkomt met specifieke browserpatronen, en retourneert het schoongemaakte versienummer of void als er geen overeenkomst is.
if (!$this->source) return void
if (preg_match('/(?:Edg|OPR|Chrome|Firefox|Version|CriOS|FxiOS|SamsungBrowser)\/([0-9.]+)/', $this->source, $m)){
$v = $m[1]
$v = preg_replace('/[^0-9.].*/', void, $v)
$v = preg_replace('/(?:\.0)+$/', void, $v)
return $v
}
return voidprop
%useragent -> full
line 76
Geeft de volledige user agent-string terug, inclusief de naam en versie van de user agent, geformatteerd om onnodige delen uit te sluiten.
if (!$this->name) return 'Unknown'
$v = $this->version
if (!$v) return $this->name
$short = preg_replace('/^(\d+\.\d+).*/','$1',$v)
if (preg_match('/\.0$/',$short)) $short = preg_replace('/\.0$/', void, $short)
return rtrim($this->name.space.$short)prop
%useragent -> device
line 85
Bepaalt het type apparaat (Tablet, Telefoon of Desktop) op basis van de user agent-string die is opgeslagen in de source-eigenschap.
if (!$this->source) return 'Unknown'
if (preg_match('/iPad|Tablet|Tab|SM-T|Nexus 7|Nexus 10/i', $this->source)) return 'Tablet'
if (preg_match('/Mobile|iPhone|Android.*Mobile|SM-G|Pixel [0-9]/i', $this->source)) return 'Phone'
return 'Desktop'object
%visitors
/phlo/resources/visitors.phlo
static
visitors :: table
line 11
De 'visitors::$table' verwijst naar de database tabel die is gekoppeld aan de 'visitors' resource in Phlo.
'visitors'static
visitors :: columns
line 12
Definieert de kolommen voor de visitors resource, waarbij de te gebruiken attributen voor gegevensbewerkingen worden gespecificeerd.
'id,token,host,page,lang,IP,browser,os,device,active_seconds,state,width,height,referrer,created,changed'static
visitors :: history
line 14
Haal een geschiedenis op van bezoekersaantallen, gegroepeerd op datum en tel unieke tokens en totale bezoeken.
static::records(columns: 'FROM_UNIXTIME(changed, "%Y-%m-%d") AS date,COUNT(DISTINCT token) AS visitors,COUNT(id) AS visits', group: 'date', order: 'date DESC')static
visitors :: online
line 15
Dit haalt het aantal unieke online bezoekers op die in de afgelopen 9 seconden zijn veranderd.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 9)')static
visitors :: lastHour
line 16
Haal het aantal unieke bezoekers (tokens) op die in het afgelopen uur zijn veranderd.
static::item(columns: 'COUNT(DISTINCT token)', where: 'changed >= (UNIX_TIMESTAMP() - 3600)')static
visitors :: isBot (?string $ua):bool
line 18
Bepaalt of de user agent-string aangeeft dat de bezoeker een bot is door deze te vergelijken met een vooraf gedefinieerd regex-patroon.
if (!$ua) return false
return (bool)preg_match('/bot|crawl|spider|slurp|baiduspider|facebookexternalhit|twitterbot|linkedinbot|curl|wget|python-requests|go-http-client|java\//i', $ua)static
visitors :: parseReferrer (string $url):string
line 23
Analyseert de referrer-URL om de gebruikte zoekmachine te identificeren en retourneert een geformatteerde string die de zoekmachine aangeeft of de host als er geen overeenkomst wordt gevonden.
static $engines = ['google' => 'Google', 'bing' => 'Bing', 'duckduckgo' => 'DuckDuckGo', 'yahoo' => 'Yahoo', 'baidu' => 'Baidu', 'yandex' => 'Yandex', 'ecosia' => 'Ecosia', 'startpage' => 'Startpage', 'brave' => 'Brave', 'kagi' => 'Kagi']
$host = strtolower(preg_replace('/^www\./', void, (string)(parse_url($url, PHP_URL_HOST) ?? void)))
foreach ($engines AS $key => $name) if (str_contains($host, $key)) return 'search:'.$name
return $host ?: substr($url, 0, 100)route
route PUT heartbeat @n,v,l,u,w,h,a,p,r,c,s,pp,ps
line 30
Behandelt het PUT-verzoek voor het bijwerken van hartslaginformatie van bezoekers, inclusief toestemming, sessieduur en pagina-interacties, terwijl de gegevens in de database worden opgeslagen.
if (static::isBot(%useragent->source)) return
$consent = (bool)%payload->c
$n = strlen(%payload->n) === 8 ? %payload->n : date('Ymd')
$id = $consent ? token(20, $n.space.%cookies->token.space.%useragent->source) : token(20, $n.space.date('Ymd').space.%cookies->token.space.%req->ip)
$delta = max(0, min(120, (int)%payload->s))
$prevDelta = max(0, min(120, (int)%payload->ps))
$lang = strlen(%payload->l) === 2 ? %payload->l : %app->lang ?? 'en'
$referrer = ($r = (string)%payload->r) && !str_contains($r, host) ? static::parseReferrer($r) : null
static::DB()->query('INSERT INTO '.static::$table.' (id, token, host, page, lang, IP, browser, os, device, active_seconds, state, width, height, referrer, created, changed) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE token = VALUES(token), host = VALUES(host), page = VALUES(page), lang = VALUES(lang), IP = VALUES(IP), browser = VALUES(browser), os = VALUES(os), device = VALUES(device), active_seconds = active_seconds + VALUES(active_seconds), state = VALUES(state), width = VALUES(width), height = VALUES(height), referrer = IF(referrer IS NULL OR referrer = '.sq.sq.', VALUES(referrer), referrer), changed = VALUES(changed)', $id, token(20, (string)(%cookies->token ?? %payload->n)), host, %payload->u, $lang, $consent ? %req->ip : void, $consent ? %useragent->full.(%payload->a ? ' App' : void) : substr(md5((string)%useragent->source), 0, 8), $consent ? %useragent->osFull : void, $consent ? %useragent->device : void, $delta + $prevDelta, %payload->v, %payload->w, %payload->h, $referrer, time(), time())
$pages = preg_replace('/visitors$/', 'visitor_pages', (string)static::$table)
if (strlen((string)%payload->p) >= 8){
$pv = token(20, $id.space.%payload->p)
static::DB()->query('INSERT INTO '.$pages.' (id, visitor, host, page, lang, active_seconds, beats, created, changed) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE active_seconds = active_seconds + VALUES(active_seconds), beats = beats + 1, changed = VALUES(changed)', $pv, $id, host, %payload->u, $lang, $delta, 1, time(), time())
}
if (strlen((string)%payload->pp) >= 8){
$ppv = token(20, $id.space.%payload->pp)
static::DB()->query('UPDATE '.$pages.' SET active_seconds = active_seconds + ?, beats = beats + 1, changed = ? WHERE id = ?', $prevDelta, time(), $ppv)
}view
script
line 51
Volgt gebruikersactiviteit op de pagina door actieve tijd te meten en heartbeat-gegevens naar de server te verzenden, inclusief gebruikersstatus en zichtbaarheidstoestand.
let curpath = app.path
let pv = phlo.token(12)
let hbTimer, activeMs = 0, activeAt = null, prev = null
const visible = () => document.visibilityState === 'visible'
const accrue = () => {
if (activeAt === null) return
activeMs += performance.now() - activeAt
activeAt = visible() ? performance.now() : null
}
const flush = beacon => {
accrue()
const consent = document.cookie.includes('cookieChoice=all')
window.name ||= phlo.token(8)
const s = Math.floor(activeMs / 1000)
activeMs -= s * 1000
const body = JSON.stringify({n: window.name, v: app.state, l: obj('html').lang ?? 'en', u: app.path, w: innerWidth, h: innerHeight, a: app.mode, p: pv, r: (r = document.referrer) ? (r === `${location.origin}/` ? null : r) : null, c: consent ? 1 : 0, s, pp: prev ? prev.p : null, ps: prev ? prev.s : null})
prev = null
fetch('/heartbeat', {method: 'PUT', headers: {'Content-Type': 'application/json'}, body, keepalive: !!beacon})
}
const heartbeat = () => delay('heartbeat', 333, () => {
clearTimeout(hbTimer)
flush()
if (visible()) hbTimer = setTimeout(heartbeat, 20000)
})
document.addEventListener('visibilitychange', () => {
if (!visible()) return flush(true)
activeAt = performance.now()
heartbeat()
})
addEventListener('resize', heartbeat)
addEventListener('pagehide', () => flush(true))
app.updates.push(() => {
if (curpath === app.path) return
if (prev) flush()
accrue()
prev = {p: pv, s: Math.floor(activeMs / 1000)}
pv = phlo.token(12)
activeMs = 0
curpath = app.path
heartbeat()
})
if (visible()) activeAt = performance.now()
heartbeat()object
%websocket
/phlo/resources/websocket.phlo
static
websocket :: connect ($wsHost, $wsToken, $wsSocket)
line 10
Stelt een WebSocket-verbinding tot stand met behulp van de opgegeven host, token en socketparameters.
!function_exists('wsConnect') || wsConnect($wsHost, $wsToken, $wsSocket)static
websocket :: auth ($wsHost, $wsToken, $wsSocket)
line 11
Controleert of de 'wsAuth'-functie bestaat en roept deze, indien niet, aan met de opgegeven parameters voor WebSocket-authenticatie.
!function_exists('wsAuth') || wsAuth($wsHost, $wsToken, $wsSocket)static
websocket :: receive ($wsHost, $wsToken, $wsSocket, $data)
line 12
Ontvangt gegevens van een WebSocket-verbinding met behulp van de opgegeven host, token en socket, en verwerkt de binnenkomende gegevens als JSON.
function_exists('wsReceive') && wsReceive($wsHost, $wsToken, $wsSocket, ...json_decode($data, true))static
websocket :: close ($wsHost, $wsToken, $wsSocket)
line 13
Sluit een WebSocket-verbinding met behulp van de opgegeven host, token en socket.
function_exists('wsClose') && wsClose($wsHost, $wsToken, $wsSocket)object%WhatsApp
/phlo/resources/WhatsApp.phlo
method
%WhatsApp -> __construct (public string $url, public string $secret)
line 10
Initialiseert een WhatsApp-instantie met een opgegeven URL en geheim, waarbij wordt gegarandeerd dat de URL eindigt met een schuine streep.
$this->url = rtrim($url, slash).slashstatic
WhatsApp :: channel ($channel)
line 12
Maakt een nieuwe instantie van het WhatsApp-kanaal met de opgegeven URL en geheim, standaard 'http://localhost:8081' en 'void' als niet gespecificeerd.
new static($channel->configData->url ?? 'http://localhost:8081', $channel->secretData->secret ?? void)method
%WhatsApp -> number ($contact)
line 14
Extraheert het telefoonnummer uit een WhatsApp-contactstring en retourneert een fout als het formaat ongeldig is.
($pos = strpos($contact, '@')) ? substr($contact, 0, $pos) : error('Invalid contact: '.esc($contact))method
%WhatsApp -> isGroup ($contact)
line 15
Controleert of de opgegeven contactpersoon een WhatsApp-groep is door te verifiëren of de contactpersoon een '@g' achtervoegsel bevat.
last($this->number($contact), (bool)strpos($contact, '@g'))method
%WhatsApp -> status
line 17
Haal de huidige status van WhatsApp op met een GET-verzoek.
$this->request('status', GET: true)method
%WhatsApp -> health
line 18
Controleert de gezondheidsstatus van de WhatsApp-service door een GET-verzoek te verzenden.
$this->request('health', GET: true)method
%WhatsApp -> qr
line 19
Stuurt een verzoek om de QR-code voor WhatsApp-authenticatie op te halen.
$this->request('qr', GET: true)method
%WhatsApp -> disconnect
line 20
Verbreekt de huidige WhatsApp-sessie door een verbreekverzoek te sturen.
$this->request('disconnect')method
%WhatsApp -> read ($chat)
line 22
Stuurt een verzoek om berichten te lezen uit een opgegeven WhatsApp-chat.
$this->request('read', chat: $chat)method
%WhatsApp -> reaction ($msg, $emoji)
line 23
Stuurt een reactie-emoticon naar een specifiek bericht in WhatsApp.
$this->request('reaction', msg: $msg, emoji: $emoji)method
%WhatsApp -> text ($to, $text)
line 25
Verzendt een tekstbericht naar de opgegeven ontvanger via WhatsApp.
$this->request('text', to: $to, text: $text)method
%WhatsApp -> image ($to, file $file, $text = void)
line 26
Verzendt een afbeeldingsbericht via WhatsApp naar de opgegeven ontvanger, met de optie om een tekstbericht toe te voegen.
$this->request('image', to: $to, filename: $file->name, image: $file->src, text: $text)method
%WhatsApp -> location ($to, $lat, $lon, $text)
line 27
Verzendt een locatiebericht via WhatsApp naar de opgegeven ontvanger met breedtegraad, lengtegraad en optionele tekst.
$this->request('location', to: $to, lat: $lat, lon: $lon, text: $text)method
%WhatsApp -> document ($to, file $file, $text = void)
line 28
Verzendt een document via WhatsApp naar de opgegeven ontvanger, inclusief een optioneel tekstbericht.
$this->request('document', to: $to, filename: $file->name, document: $file->src, text: $text)method
%WhatsApp -> audio ($to, file $file)
line 30
Verzendt een audiobericht naar een opgegeven ontvanger met het opgegeven audiobestand.
$this->request('audio', to: $to, audio: $file->src)method
%WhatsApp -> voice ($to, file $file)
line 31
Verzendt een spraakbericht naar een opgegeven ontvanger met behulp van het opgegeven audiobestand.
$this->request('voice', to: $to, audio: $file->src)method
%WhatsApp -> poll ($to, $name, array $options, bool $multi = false)
line 33
Verzendt een peilingbericht naar een opgegeven WhatsApp-ontvanger met de gegeven opties, met de mogelijkheid voor meerdere selecties indien opgegeven.
$this->request('poll', to: $to, name: $name, options: $options, multi: $multi)method
%WhatsApp -> startTyping ($to)
line 35
Start de typindicator voor een opgegeven ontvanger in WhatsApp.
$this->request('typing/start', to: $to)method
%WhatsApp -> stopTyping ($to)
line 36
Stop de typindicator voor een specifieke ontvanger in WhatsApp.
$this->request('typing/stop', to: $to)method
%WhatsApp -> request ($action, ...$data)
line 38
Verzendt een verzoek naar de WhatsApp API met de opgegeven actie en gegevens, en retourneert een responsobject dat succes of falen aangeeft.
$get = $data['GET'] ?? false
unset($data['GET'])
$raw = trim((string)HTTP($this->url.$action, ['secret: '.$this->secret], true, $get ? null : $data))
if (strtolower($raw) === 'ok') return obj(ok: true)
$res = json_decode($raw)
if (!$res && $raw) return obj(ok: false, error: $raw)
return $res ?: obj(ok: false, error: 'Empty WhatsApp response')Functions
function
active(bool $cond, string $classList = void)
/phlo/resources/active.phlo line 8
Genereert een class-attribuutstring voor HTML-elementen, waarbij 'active' aan de opgegeven classlijst wordt toegevoegd als de voorwaarde waar is.
$cond || $classList ? ' class="'.$classList.($cond ? ($classList ? space : void).'active' : void).'"' : voidfunction
age(int $time)
/phlo/resources/age.phlo line 8
Berechnet de leeftijd door de gegeven tijd van de huidige tijd af te trekken.
time() - $timefunction
age_human(int $age)
/phlo/resources/age.human.phlo line 9
Berechnet een leesbare tijdsduur op basis van de gegeven leeftijd in seconden.
time_human(time() - $age)function
apcu($key, $cb, int $duration = 3600, bool $log = true)
/phlo/resources/apcu.phlo line 9
Cache een waarde met behulp van APCu met een opgegeven sleutel en callback, met een duur en optionele logging.
first($value = apcu_entry($key, $cb, $duration), $log && debug('C: '.(strlen($key) > 58 ? substr($key, 0, 55).'...' : $key).(is_array($value) ? ' ('.count($value).')' : (is_numeric($value) ? ":$value" : (is_string($value) ? ':string:'.strlen($value) : colon.gettype($value))))))function
await(...$jobs)
/phlo/resources/await.phlo line 10
Wacht op meerdere taken om te voltooien, beheert hun uitvoer en fouten, en retourneert de resultaten. Het beheert kindprocessen en zorgt ervoor dat ze niet eindeloos blokkeren.
if (daemon) return daemon::await($jobs)
$children = []
$open = []
foreach ($jobs AS $i => $job){
[$cb, $args] = is_array($job) ? [$job[0], array_slice($job, 1)] : [$job, []]
$cmd = cli.space.escapeshellarg($_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void)
$desc = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']]
$proc = proc_open($cmd, $desc, $pipes)
fclose($pipes[0])
stream_set_blocking($pipes[1], false)
stream_set_blocking($pipes[2], false)
$children[$i] = obj(proc: $proc, out: $pipes[1], err: $pipes[2], stdout: void, stderr: void)
$open['o'.$i] = $pipes[1]
$open['e'.$i] = $pipes[2]
}
// Drain every child's stdout AND stderr together: reading one stream to EOF before the
// other deadlocks a child that fills the unread pipe. Bound the whole wait so a hung
// child cannot block the caller forever.
$deadline = time() + (defined('await_timeout') ? await_timeout : 300)
while ($open){
$read = $open
$write = $except = []
if (@stream_select($read, $write, $except, 1) === false) break
foreach ($read AS $key => $stream){
$chunk = fread($stream, 65536)
if ($chunk === void || $chunk === false){
feof($stream) && $open = array_diff_key($open, [$key => 1])
continue
}
$i = substr($key, 1)
if ($key[0] === 'o') $children[$i]->stdout .= $chunk
else $children[$i]->stderr .= $chunk
}
if (time() >= $deadline) break
}
// Terminate any child still running (deadline or a stream_select error). SIGKILL cannot
// be ignored, so proc_close below will not block on a process that drops SIGTERM.
foreach ($children AS $child) (proc_get_status($child->proc)['running'] ?? false) && proc_terminate($child->proc, 9)
$results = []
foreach ($children AS $i => $child){
fclose($child->out)
fclose($child->err)
$code = proc_close($child->proc)
$err = trim($child->stderr)
if ($err !== void){
$ej = json_decode($err, true)
$results[$i] = json_last_error() === JSON_ERROR_NONE ? $ej : $err
continue
}
if ($code !== 0){
$results[$i] = obj(error: 'CLI process failed', code: $code)
continue
}
$json = json_decode($child->stdout, true)
$results[$i] = json_last_error() === JSON_ERROR_NONE ? $json : $child->stdout
}
return $resultsfunction
button(...$args):string
/phlo/resources/tags.form.phlo line 10
Maakt een knop element met de opgegeven argumenten die als props worden doorgegeven.
tag('button', ...$args)function
camel(string $text)
/phlo/resources/camel.phlo line 8
Converteert een gegeven string naar camel case door de eerste letter van elk woord te kapitaliseren en spaties te verwijderen.
lcfirst(str_replace(space, void, ucwords(lcfirst($text))))function
chunk(...$cmds):void
/phlo/resources/chunk.phlo line 9
Deze functie verzendt een gegevenschunk naar de client, waarbij streaming wordt geïnitialiseerd als dit nog niet is gebeurd en de juiste headers voor de respons worden ingesteld.
$res = %res
$cli = %req->cli
!$res->streaming && $res->done && error('Output already started, invalid chunk()')
if (debug){
$res->dump && [$cmds['dump'] = $res->dump, $res->dump = []]
$res->debug && [$cmds['debug'] = $res->debug, $res->debug = []]
}
if (!$res->streaming){
$res->streaming = true
$res->type = 'application/x-ndjson'
$res->header('Cache-Control', 'no-store')
$res->header('X-Content-Type-Options', 'nosniff')
$res->render()
}
print(json_encode($cmds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf)
$cli || [@ob_flush(), flush()]function
create(iterable $items, \Closure $keyCb, ?\Closure $valueCb = null)
/phlo/resources/create.phlo line 8
Maakt een associatieve array door de waarden uit de iterable als sleutels te gebruiken en de optionele waarde callback te gebruiken om de bijbehorende waarden te bepalen.
array_combine(loop($items, $keyCb), $valueCb ? loop($items, $valueCb) : $items)function
en($text, ...$args)
/phlo/resources/lang.phlo line 12
Deze functie haalt een vertaling op voor de opgegeven tekst in het Engels, en kan optioneel worden opgemaakt met extra argumenten.
%lang->translation('en', $text, ...$args)function
exec_stream(string $cmd, ?int $timeoutSec = 0)
/phlo/resources/exec.stream.phlo line 9
Voert een opdracht uit in een apart proces en streamt de uitvoer en foutmeldingen asynchroon, waarbij ze als objecten worden opgeleverd. Het ondersteunt ook een time-outfunctie om het proces te beëindigen als het de opgegeven duur overschrijdt.
$desc = [0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']]
$proc = proc_open($cmd, $desc, $pipes)
if (!is_resource($proc)) return
stream_set_blocking($pipes[1], false)
stream_set_blocking($pipes[2], false)
$bufOut = void
$bufErr = void
while (true){
$status = proc_get_status($proc)
$running = $status['running']
$read = []
$w = null
$e = null
if (!feof($pipes[1])) $read[] = $pipes[1]
if (!feof($pipes[2])) $read[] = $pipes[2]
if ($read) @stream_select($read, $w, $e, 0, 200000)
foreach ($read AS $r){
$chunk = fread($r, 8192)
if ($chunk === void || $chunk === false) continue
if ($r === $pipes[1]){
$bufOut .= $chunk
while (($pos = strpos($bufOut, lf)) !== false){
$line = substr($bufOut, 0, $pos)
$bufOut = substr($bufOut, $pos + 1)
yield obj(data: $line)
}
}
else {
$bufErr .= $chunk
while (($pos = strpos($bufErr, lf)) !== false){
$line = substr($bufErr, 0, $pos)
$bufErr = substr($bufErr, $pos + 1)
yield obj(data: $line, error: true)
}
}
}
if (!$running) break
if ($timeoutSec > 0 && ($status['running_time'] ?? 0) > $timeoutSec){
proc_terminate($proc)
yield obj(data: 'process timeout', error: true)
break
}
}
if ($bufOut !== void) yield obj(data: $bufOut)
if ($bufErr !== void) yield obj(data: $bufErr, error: true)
foreach ($pipes AS $p) @fclose($p)
proc_close($proc)function
HTTP(string $url, array $headers = [], bool $JSON = false, $POST = null, $PUT = null, $PATCH = null, $QUERY = null, bool $DELETE = false, string|bool|null $agent = null, string|bool $cookies = false, int $timeout = 15, &$response = null)
/phlo/resources/HTTP.phlo line 9
$curl = curl_init($url)
if ($POST !== null || $PUT !== null || $PATCH !== null || $QUERY !== null){
if (!is_null($POST)) [$method = 'POST', $content = $POST]
elseif (!is_null($PUT)) [$method = 'PUT', $content = $PUT]
elseif (!is_null($PATCH)) [$method = 'PATCH', $content = $PATCH]
elseif (!is_null($QUERY)) [$method = 'QUERY', $content = $QUERY]
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method)
if ($JSON) [!is_string($content) && $content = json_encode($content), array_push($headers, 'Content-Type: application/json', 'Content-Length: '.strlen($content))]
curl_setopt($curl, CURLOPT_POSTFIELDS, $content)
}
elseif ($DELETE) curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE')
$agent && curl_setopt($curl, CURLOPT_USERAGENT, $agent === true ? phlo('req')->userAgent : $agent)
if ($cookies !== false) [$jar = $cookies === true ? data.'cookies.txt' : $cookies, curl_setopt($curl, CURLOPT_COOKIEFILE, $jar), curl_setopt($curl, CURLOPT_COOKIEJAR, $jar)]
$resHeaders = []
curl_setopt_array($curl, [CURLOPT_HTTPHEADER => $headers, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => $timeout, CURLOPT_ENCODING => void, CURLOPT_HEADERFUNCTION => function($ch, $line) use (&$resHeaders){
$parts = explode(colon, $line, 2)
count($parts) === 2 && $resHeaders[strtolower(trim($parts[0]))] = trim($parts[1])
return strlen($line)
}])
$res = curl_exec($curl)
$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE)
$response = obj(ok: $res !== false && $status >= 200 && $status < 300, status: $status, headers: $resHeaders, error: $res === false ? curl_error($curl) : null)
if ($res === false) error('HTTP error: '.curl_error($curl))
return $resfunction
input(...$args):string
/phlo/resources/tags.form.phlo line 11
Maakt een invoerelement in de view met de opgegeven argumenten.
tag('input', ...$args)function
n8n($webhook, ?array $data = null, $test = false)
/phlo/resources/n8n.phlo line 9
Stuurt een HTTP POST-verzoek naar een n8n webhook met optionele gegevens en een testvlag.
HTTP(%creds->n8n->server.'webhook'.($test ? '-test' : '').'/'.$webhook, POST: $data)function
nl($text, ...$args)
/phlo/resources/lang.phlo line 11
Vertaal de gegeven tekst naar het Nederlands met de opgegeven argumenten voor opmaak.
%lang->translation('nl', $text, ...$args)function
notify(string $title, string $body = void, string $type = 'info', string $level = 'info', ?string $user = null):void
/phlo/resources/notify.phlo line 8
Verzendt een melding met een opgegeven titel, inhoud, type, niveau en optionele gebruiker naar een geconfigureerde URL via HTTP.
$cfg = %creds->notify ?? null
if (!$cfg) return
$url = $cfg->url ?? void
$secret = $cfg->secret ?? void
if ($url === void || $secret === void) return
try {
HTTP($url, ['secret: '.$secret], true, [
'app' => (string)($cfg->app ?? (defined('id') ? id : 'app')),
'server' => $cfg->server ?? 'local',
'host' => %req->host ?? void,
'type' => $type,
'level' => $level,
'title' => $title,
'body' => $body,
'user' => $user,
])
}
catch (\Throwable $e){}function
phlo(?string $phloName = null, ...$args):mixed
/phlo/phlo.php line 240
Maakt of haalt een instantie van een Phlo-object op op basis van de opgegeven naam en argumenten, en beheert een statische lijst van objecten voor hergebruik.
static $list = [];
if ($phloName === 'tech/reset'){
obj::$classProps = [];
return array_keys($list = array_filter($list, static fn($obj) => $obj->objPers));
}
if ($phloName === null) return array_keys($list);
$class = strtr($phloName, [slash => us]);
$handle = method_exists($class, '__handle') ? $class::__handle(...$args) : ($args ? null : $phloName);
if ($handle === true){
if (isset($list[$phloName])) return $list[$phloName]->objImport(...$args);
$handle = $phloName;
}
elseif ($handle && isset($list[$handle])) return $list[$handle];
$object = new $class(...$args);
if ($handle) $list[$handle] = $object;
if ($object->hasMethod('controller') && (!phlo('req')->cli || $phloName !== 'app')) $object->controller();
return $object;function
phlo_app(...$args):void
/phlo/phlo.php line 38
Initialiseert een Phlo-applicatie door de omgeving in te stellen, noodzakelijke klassen en functies te laden, en verschillende runtime-parameters te configureren op basis van de opgegeven argumenten.
if ($args['trace'] ??= false) require_once __DIR__.'/classes/trace.php';
require_once __DIR__.'/functions'.($args['trace'] ? '.trace.php' : '.php');
require_once __DIR__.'/classes/obj.php';
require_once __DIR__.'/classes/req.php';
require_once __DIR__.'/classes/res.php';
$args['app'] ?? error('No "app" path defined');
$args['debug'] ??= false;
$args['build'] ??= false;
$args['host'] ??= null;
$args['control'] ??= ($args['build'] && $args['debug']) ? 'phlo' : false;
$args['auth'] ??= false;
$args['data'] ??= $args['app'].'data/';
$args['php'] ??= $args['app'].'php/';
$args['www'] ??= $args['app'].'www/';
$args['cli'] ??= ZEND_THREAD_SAFE ? 'php-zts' : 'php';
$args['thread'] ??= false;
$args['daemon'] ??= false;
$args['build'] && $args['thread'] && error('Phlo build and thread mode cannot be combined');
$args['build'] && !is_file($args['data'].'app.json') && error('Phlo build mode requires data/app.json');
$args['auth'] && !$args['build'] && error('Auth requires build mode');
foreach ($args as $key => $value) define($key, $value);
define('engine', __DIR__.slash);
if ($args['debug']) require_once __DIR__.'/debug.php';
if ($args['build']) require_once __DIR__.'/classes/changed.php';
if ($args['daemon']) require_once __DIR__.'/classes/daemon.php';
if ($args['trace']) trace::boot($args['app']);
set_error_handler(static function(int $level, string $msg, string $file = '', int $line = 0):bool {
if (!(error_reporting() & $level)) return false;
throw new ErrorException($msg, 0, $level, $file, $line);
});
set_exception_handler('phlo_exception');
spl_autoload_register(static function(string $class):void {
static $map = null, $mtime = null;
$file = php.'classmap.php';
if ($map === null || $mtime !== (is_file($file) ? filemtime($file) : null)){
$map = is_file($file) ? require $file : [];
$mtime = is_file($file) ? filemtime($file) : null;
}
if (isset($map[$class])){ require_once php.$map[$class]; return; }
});
if ($args['build']){
$engineMap = ['build' => 'build', 'reflect' => 'reflect', 'build_file' => 'file', 'build_node' => 'node', 'build_builder' => 'builder', 'build_css' => 'css', 'build_icons' => 'icons'];
spl_autoload_register(static function(string $class) use ($engineMap):void {
$name = $engineMap[strtolower($class)] ?? null;
if ($name !== null) require_once engine.'classes/'.$name.'.php';
});
}
defined('composer') && spl_autoload_register(static function(string $class):void {
static $loaded = false;
if ($loaded) return;
$loaded = true;
require_once composer.'vendor/autoload.php';
foreach (spl_autoload_functions() as $fn){
if (is_array($fn) && ($fn[0] ?? null) instanceof \Composer\Autoload\ClassLoader){
spl_autoload_unregister($fn);
spl_autoload_register($fn);
$fn[0]->loadClass($class);
return;
}
}
});
if ($args['thread'] !== false && PHP_SAPI !== 'cli'){
ignore_user_abort(true);
$handle = static function():void { phlo_thread(); };
for ($i = 1; !$args['thread'] || $i <= $args['thread']; ++$i){
$keepRunning = frankenphp_handle_request($handle);
phlo('tech/reset');
if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
gc_collect_cycles();
if (!$keepRunning) break;
}
return;
}
phlo_thread();function
phlo_async(string $cb, ...$args)
/phlo/resources/phlo.async.phlo line 9
Voert een callbackfunctie asynchroon uit, waarbij eventuele extra argumenten worden doorgegeven, en retourneert de proces-ID als het succesvol is.
if (daemon) return daemon::fire($cb, $args)
$cmd = cli.space.escapeshellarg($_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void).' > /dev/null 2>&1 & echo $!'
exec($cmd, $r)
return isset($r[0]) && ctype_digit($r[0]) && (int)$r[0] > 0function
phlo_cli(array $args):void
/phlo/phlo.php line 190
Verwerkt commandoregelargumenten, dispatcht een doelcommando en drukt het resultaat af in JSON-indeling.
if (!$args) return;
$target = array_shift($args);
$result = phlo_dispatch($target, $args);
if (isset($result)) print(json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);function
phlo_dispatch(string $target, array $args = []):mixed
/phlo/phlo.php line 177
Verzendt een methode-aanroep naar een object of een statische methode van een klasse op basis van de opgegeven doelsstring, met optioneel doorgegeven argumenten.
if (str_contains($target, dot)){
[$object, $method] = explode(dot, $target, 2);
$handle = phlo($object);
return $args ? $handle->$method(...$args) : ($handle->hasMethod($method) ? $handle->$method() : $handle->$method);
}
if (str_contains($target, '::')){
[$class, $method] = explode('::', $target, 2);
return $class::$method(...$args);
}
return $target(...$args);function
phlo_exception(Throwable $e):void
/phlo/phlo.php line 33
Behandelt uitzonderingen door het Throwable-object door te geven aan de phlo_error_handle functie voor verwerking.
require_once engine.'error.php';
phlo_error_handle($e);function
phlo_exists(string $obj)
/phlo/resources/phlo.exists.phlo line 8
Controleert of een specifiek PHP-bestand bestaat op het opgegeven objectpad.
is_file(php.strtr($obj, [us => dot]).'.php')function
phlo_load(bool $http):void
/phlo/phlo.php line 153
Laadt de noodzakelijke runtime-bestanden voor de applicatie, waarbij ervoor wordt gezorgd dat de applicatie slechts eenmaal wordt geladen en dat het juiste contenttype wordt ingesteld voor HTTP-responses.
static $loaded = false, $loadedApp = null;
if ($loaded && $loadedApp === app){
if ($http && !phlo('res')->type) phlo('res')->type = 'text/html; charset=UTF-8';
return;
}
if (build && (!is_file(php.'functions.php') || !is_file(php.'app.php') || build_base::changed())){
debug('Builder started');
$changed = build::run();
$changed && debug('Built '.implode(', ', array_map('basename', $changed)).' ('.count($changed).')');
}
if (!is_file(php.'functions.php') || !is_file(php.'app.php')) error('Compiled runtime not available');
if (!$loaded){
require_once php.'functions.php';
$loaded = true;
}
if ($loadedApp !== app){
require_once php.'app.php';
$loadedApp = app;
}
if ($http && !phlo('res')->type) phlo('res')->type = 'text/html; charset=UTF-8';function
phlo_serve():void
/phlo/phlo.php line 197
Start een Phlo-server die luistert naar binnenkomende berichten, deze verwerkt en antwoorden terugstuurt in JSON-formaat. Het beheert zowel synchrone als asynchrone verzoeken, inclusief outputstreaming en foutrapportage.
ini_set('display_errors', 'stderr');
stream_set_blocking(STDIN, true);
fwrite(STDOUT, json_encode(['t' => 'ready']).lf);
while (($line = fgets(STDIN)) !== false){
$line = trim($line);
if ($line === void) continue;
$msg = json_decode($line, true) ?: [];
$id = $msg['id'] ?? null;
$target = (string)($msg['target'] ?? void);
$args = (array)($msg['args'] ?? []);
$stream = (bool)($msg['stream'] ?? false);
$lineBuf = void;
$emit = static function(string $chunk) use (&$lineBuf, $id):string {
$lineBuf .= $chunk;
while (($pos = strpos($lineBuf, lf)) !== false){
$out = substr($lineBuf, 0, $pos);
$lineBuf = substr($lineBuf, $pos + 1);
fwrite(STDOUT, json_encode(['id' => $id, 't' => 'line', 'data' => $out], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
}
return void;
};
try {
if ($target === void) error('No target');
if ($stream){
ob_start($emit, 1);
$result = phlo_dispatch($target, $args);
while (ob_get_level()) ob_end_flush();
if ($lineBuf !== void) fwrite(STDOUT, json_encode(['id' => $id, 't' => 'line', 'data' => $lineBuf], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
}
else $result = phlo_dispatch($target, $args);
fwrite(STDOUT, json_encode(['id' => $id, 't' => 'done', 'result' => $result], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
}
catch (Throwable $e){
while (ob_get_level()) ob_end_clean();
fwrite(STDOUT, json_encode(['id' => $id, 't' => 'error', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).lf);
}
phlo('tech/reset');
if (session_status() === PHP_SESSION_ACTIVE) session_write_close();
gc_collect_cycles();
}function
phlo_stream(string $cb, ...$args)
/phlo/resources/phlo.stream.phlo line 10
Stroomt uitvoer van een callbackfunctie, waardoor asynchrone verwerking van gegevens mogelijk is. Het kan in een daemon-modus werken of een opdracht in de CLI-context uitvoeren.
if (daemon){
yield from daemon::stream($cb, $args)
return
}
yield from exec_stream(cli.space.escapeshellarg($_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void))function
phlo_sync(string $cb, ...$args)
/phlo/resources/phlo.sync.phlo line 9
Voert een callbackfunctie synchronisch uit, waarbij eventuele extra argumenten worden doorgegeven, en retourneert het resultaat als een JSON-object of een foutmelding als de uitvoering mislukt.
if (daemon) return daemon::run($cb, $args)
$cmd = cli.space.escapeshellarg($_SERVER['SCRIPT_FILENAME']).space.escapeshellarg($cb).loop($args, fn($a) => space.escapeshellarg((string)$a), void)
exec($cmd.' 2>&1', $r, $code)
$out = implode(lf, $r)
if ($code !== 0) error('Could not execute "'.esc($cb).'" via CLI')
$j = json_decode($out, true)
if (json_last_error() !== JSON_ERROR_NONE) return $out
if (is_array($j) && isset($j['error'])) error($j['error'])
return $jfunction
phlo_thread():void
/phlo/phlo.php line 115
Beheert de hoofd uitvoeringsstroom van een Phlo-applicatie, inclusief verzoeken voor zowel CLI- als webomgevingen, met inbegrip van authenticatie en dashboardweergave.
try {
$req = phlo('req');
if ($req->cli){
$target = $req->args[0] ?? void;
if (str_starts_with($target, 'build::') || str_starts_with($target, 'reflect::')){
phlo_cli($req->args);
return;
}
phlo_load(false);
phlo('app');
phlo_cli($req->args);
return;
}
$isDashboard = build && debug && control && str_starts_with($req->path.slash, control.slash);
if (auth && !$isDashboard){
phlo_auth('site', 'Phlo App - '.host);
if (phlo('res')->done) return;
}
if ($isDashboard){
require_once engine.'control.php';
phlo_dashboard::handle(substr($req->path, strlen(control) + 1));
phlo('res')->render();
return;
}
phlo_load(true);
phlo('app');
phlo('res')->render();
}
catch (RuntimeException $e){
if ($e->getMessage() === 'PhloDump') return;
phlo_exception($e);
}
catch (Throwable $e){
phlo_exception($e);
}function
select(...$args):string
/phlo/resources/tags.form.phlo line 12
Maakt een 'select' HTML-element met de opgegeven argumenten als attributen en opties.
tag('select', ...$args)function
setting(?string $key = null, $value = null)
/phlo/resources/settings.phlo line 10
$store = %JSON('settings', assoc: true)
if ($key === null) return $store->objData
if (func_num_args() > 1){
$store->$key = $value
$store->objChanged && $store->objWrite($store->objData)
}
return $store->$keyfunction
slug(string $text)
/phlo/resources/slug.phlo line 8
Converteert een gegeven string naar een URL-vriendelijke slug door niet-alfanumerieke tekens te verwijderen, naar kleine letters om te zetten en spaties door streepjes te vervangen.
trim(preg_replace('/[^a-z0-9]+/', dash, strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text))), dash)function
stream($data = null, string $type = 'application/octet-stream', ?string $name = null):void
/phlo/resources/stream.phlo line 10
$res = %res
$cli = %req->cli
!$res->streaming && $res->done && error('Output already started, invalid stream()')
if (!$res->streaming){
$res->streaming = true
$res->type = $type
$res->header('Cache-Control', 'no-store')
$res->header('X-Content-Type-Options', 'nosniff')
$res->header('X-Accel-Buffering', 'no')
$name === null || $res->header('Content-Disposition', 'attachment; filename="'.str_replace('"', '', $name).'"')
$res->render()
}
if ($data === null) return
foreach (is_iterable($data) ? $data : [$data] as $part){
print((string)$part)
$cli || [@ob_flush(), flush()]
}function
tag(string $tagName, ?string $inner = null, ...$args)
/phlo/resources/tag.phlo line 9
Genereert een HTML-tag met de opgegeven naam, optionele inhoud en extra attributen.
"<$tagName".loop(array_filter($args, fn($value) => !is_null($value)), fn($value, $key) => space.strtr($key, [us => dash]).($value === true ? void : '="'.esc($value).'"'), void).'>'.(is_null($inner) ? void : "$inner</$tagName>")function
textarea(...$args):string
/phlo/resources/tags.form.phlo line 13
Maakt een 'textarea' HTML-element met de opgegeven argumenten.
tag('textarea', ...$args)function
time_human(?int $time = null)
/phlo/resources/time.human.phlo line 8
Converteert een gegeven tijdstempel naar een leesbaar tijdsverschil formaat, zoals '2 dagen' of '3 uur'. Als er geen tijdstempel wordt opgegeven, wordt de huidige tijd gebruikt.
static $labels
$labels ??= last($labels = arr(seconds: 60, minutes: 60, hours: 24, days: 7, weeks: 4, months: 13, years: 1), defined('tsLabels') && $labels = array_combine(tsLabels, $labels), $labels)
$age = time() - $time
foreach ($labels AS $range => $multiplier){
if ($age / $multiplier < 1.6583) break
$age /= $multiplier
}
return round($age)." $range"function
wsCast($wsTarget = 'all', $wsHost = host, $wsPort = daemon, ...$data)
/phlo/resources/wsCast.phlo line 9
Verzendt een WebSocket-bericht naar een opgegeven doel of naar alle doelen met behulp van de opgegeven host en poort.
HTTP (
'http://127.0.0.1:'.$wsPort.'/message',
JSON: true,
POST: arr (
host: $wsHost,
target: $wsTarget,
data: $data,
),
)