sopheos / pebble_tools
PHP helpers: arrays, strings, dates, bitwise flags, IP conversion, GPS points and Imagick images
Requires
- php: >=8.1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
Suggests
- ext-imagick: Requis par Pebble\Tools\Image (extends Imagick)
Provides
None
Conflicts
None
Replaces
None
README
Boîte à outils PHP 8.1+ : chaînes, tableaux, dates en français, drapeaux binaires, adresses IP, points GPS et images Imagick.
Chaque classe est indépendante et s'utilise surtout en statique. Aucune dépendance runtime, sauf ext-imagick pour la classe Image.
Installation
composer require sopheos/pebble_tools
Pebble\Tools\Image étend Imagick : sans l'extension imagick, charger cette classe provoque une erreur fatale. Les autres classes n'en ont pas besoin.
Claude Code
Ce package fournit un skill Claude Code dans skills/pebble-tools/. Il documente les patterns d'usage et les pièges de la librairie : T::split() inutilisable, Bitwise::add() qui ne modifie pas sa référence, Date qui vaut 1970 sur une chaîne invalide, hexadécimal IPv6 arrondi, Point::toPoints() cassé, crop() qui inverse les miroirs, etc.
Dans un projet qui dépend de sopheos/pebble_tools, copie-le une fois dans .claude/skills/ après composer install pour que Claude Code le charge automatiquement. Le nom du dossier doit correspondre au name déclaré dans SKILL.md :
cp -r vendor/sopheos/pebble_tools/skills/pebble-tools .claude/skills/pebble-tools
Pour la maintenance de la lib elle-même, voir CLAUDE.md. Les bugs connus sont listés dans TODO.md.
T (chaînes)
removeAccents(string $str) : stringRemplace toutes les lettres accentuées par leur équivalent ASCII (œ→oe).convertAccents(string $str, array $accents = T::FR) : stringPar défaut, ne convertit que les accents non français (é, è, à, ç, œ… sont conservés).alias(string $str, string $separator = '-') : stringSlug : sans accents, en minuscules, chaque caractère non alphanumérique remplacé par le séparateur, sans doublon ni séparateur aux extrémités.split(string $str, string $separator) : stringExplose et trim. Cassée : lève toujours uneTypeError(voirTODO.md).ucfirst(string $str) : string/lcfirst(string $str) : stringVersions multioctets.toCamelCase(),toPascalCase(),toSnakeCase()Conversions de casse, accents retirés.remove_emoji($string)Retire les principales plages d'emoji (pas toutes).xss(mixed $value) : stringÉchappe une chaîne,''pournull,'1'/'0'pour un booléen, JSON échappé pour un tableau ou un objet.htmlEncode(?string $value) : string/htmlDecode(?string $value) : stringhtmlspecialchars/html_entity_decodeen HTML5 UTF-8.number(float $amount, int $decimal = 2) : stringFormat français, l'espace des milliers est l'entité .money(float $amount, string $currency = ' €') : stringnumber()avec 2 décimales et la devise.
A (tableaux)
parse(mixed $value) : arrayConvertit une chaîne JSON, un tableau ou un objet en tableau associatif. Renvoie[]pour un JSON invalide ou un scalaire.equal(array $a, array $b) : boolMêmes valeurs, sans tenir compte de l'ordre ni des clés. Attention : la comparaison ne se fait que dans un sens.rand(array $data)Une valeur au hasard,nullsi le tableau est vide.unset(array $values, string $key) : arrayCopie du tableau sans la clé.unique(array $rows, string|int|null $column_key = null) : arrayValeurs uniques, réindexées, sansnull,''ni[]. Avec$column_key, travaille sur cette colonne (la colonne0est ignorée, voirTODO.md).
Bitwise
has(int $value, int $key) : boolVrai si au moins un bit de$keyest présent dans$value.add(int &$value, int $key) : intRenvoie$valueavec le bit ajouté.del(int &$value, int $key) : intRenvoie$valuesans le bit.
add() et del() ne modifient pas la variable passée, malgré la référence : utiliser la valeur de retour. Elles n'acceptent pas de littéral.
$flags = Bitwise::add($flags, self::FLAG_ADMIN);
Date
\Pebble\Tools\Date encapsule un timestamp (propriété publique timestamp).
new Date(int|string|null $timestamp = null)/Date::create(...)Maintenant si vide, sinon un timestamp ou une chaîne lue parstrtotime(). Une chaîne invalide ou numérique donne silencieusement0(1970).fromYmd(int $ymd) : staticDepuis un entier20240229.format(string $format) : stringdate()avec les noms de jours et de mois en français.ymd() : int,toSql() : string,toIso() : stringisLeap() : bool,days() : intNombre de jours du mois.- Propriétés magiques en lecture :
seconds,minutes,hours,mday,wday,mon,year,yday(commegetdate(),0pour un nom inconnu). jsonSerialize()Le timestamp et toutes ces propriétés.
Méthodes statiques :
fr(string $format, ?int $timestamp = null) : stringdate()en français (D,l,M,Ftraduits, les lettres échappées sont respectées).toFr(string $date) : stringTraduit les noms anglais d'une date déjà formatée.isLeapYear($y),daysInMonth($m, $y)seasonDate(int $year, int $season) : intDébut de la saison (Date::SPRING,SUMMER,AUTUMN,WINTER). Le jour est juste, l'heure est fausse de 20 minutes à plus de 2 heures.0pour une saison inconnue.sqlToTimestamp(),isoToTimestamp(),timestampToIso(),timestampToSql(),sqlToIso(),isoToSql(),timestampToYmd(?int $ts = null) : int
Constantes : jours (SUNDAY = 0 … SATURDAY = 6) et DAYS, mois (JANUARY = 1 …) et MONTHS, formats DATETIME_SQL, DATE_SQL, TIME_SQL.
IP
IP::fromString(string $ip),IP::fromHex(string $ip),IP::fromBin(string $ip)Fabriques. Une entrée invalide donne0.0.0.0sans erreur.toString(),toHex(),toBin()Les trois représentations.isIPv4(),isIPv6()- Helpers statiques :
isIPStr(),isIPHex(),isIPBin(),isIPv6Str(),isIPv6Hex(),isIPv6Bin(),strToBin(),binToStr(),binToHex(),hexToBin().
L'IPv4 n'est pas complétée par des zéros (1.2.3.4 → 1020304). En IPv6, toHex() et fromHex() arrondissent les adresses (et lèvent une ValueError pour certaines) : passer par toBin() / fromBin(), qui sont exacts.
Point et Points
\Pebble\Tools\Point : propriétés publiques lat et lon en degrés.
new Point(float $lat = 0, float $lon = 0),fromDegrees(),fromRadians()fromMN95(float $e, float $n)Coordonnées suisses.fromExifGPS(array $exif_gps)Depuisexif_read_data()['GPS…']. Arrondi à 7 décimales.fromDMS(array $dms)Degrés, minutes, secondes (lat_ref,lat_degrees, …). Renvoienullsi une clé manque.getLat(),getLon(),getRadLat(),getRadLon(),coordinates()([lat, lon]),latlon(),latlng(),geojson()([lon, lat])setLat(),setLon()distanceTo(Point $point)Distance en mètres (haversine).bounds(float $distance)[min, max]du carré englobant, en mètres. Faux près des pôles.insidePoly(array $coords)Polygone de[lat, lon].insidePath(object $path)Objet avecouteretinner, listes de polygones[lat, lon].insideGeoJson(array $geojson)PolygonouMultiPolygon(coordonnées[lon, lat]), trous compris.toPoints(string $str)Lit"lat,lon lat,lon". Cassée (voirTODO.md).insideVertices($x, $y, array $vertices)Algorithme du lancer de rayon.
\Pebble\Tools\Points :
add(Point $point) : static,all() : arraycenter()/getCenter(array $points)Centre de la boîte englobante ((0, 0)si vide).closer()/getCloser(Point $point, array $points)Point le plus proche (nullsi vide).
Image
\Pebble\Tools\Image étend Imagick (nécessite ext-imagick).
new Image(...$files)/Image::create(...$files)getName(),getFileSize(),getImageMimeType()(sansx-),getImageLength()setBgColor()/getBgColor()Couleur de fond pour la rotation.setQuality()/getQuality()Qualité JPEG/WebP (75).prepare()CMYK vers RGB puis suppression des EXIF (le profil ICC est gardé).isInside(int $width, int $height, bool $strict_orientation = false)/fitBounds(...)Teste ou réduit l'image dans une boîte, la boîte étant tournée pour suivre l'orientation de l'image sauf si$strict_orientation. Jamais d'agrandissement.toJpeg(),toPng(),toWebp()Changent le format ($forcepour réappliquer les options).crop(array $data)Données de cropper.js (x,y,width,height,rotate,scaleX,scaleY). Attention :scaleX/scaleYsont inversés et le canevas virtuel n'est pas réinitialisé.watermark(Image $img, int $x, int $y)save(?string $to = null) : staticÉcrit l'image, l'extension suit le format. Renvoie un nouvel objet si le chemin change.getHash()Hash perceptuel (64 caractères hexadécimaux).similarHash(string $hash1, string $hash2, float &$percent = 0) : intDistance de Hamming. Fausse dès qu'un chiffre hexadécimal est inférieur à 8 (voirTODO.md).
Shell
Shell::exec(string $command)Lance la commande en arrière-plan et rend la main tout de suite, sortie ignorée. La commande n'est pas échappée : utiliserescapeshellarg()sur chaque argument.Shell::isWin() : bool
Tests
composer install vendor/bin/phpunit
Les tests Image sont sautés sans ext-imagick. Les bugs connus sont figés par des tests annotés // BUG: qui vérifient le comportement actuel.