PIMP: PHP Image Manipulation Program ?
Et une petite classe pour traiter les images…
/**
* Image manipulation class. Requires GD.
* Some methods from php.net contribution
*/
require_once ('md/Exception.php');
class md_Image_Exception extends md_Exception{}
class md_Image
{
private $originalFilePath;
private $originalMimeType;
private $originalX;
private $originalY;
private $originalBuffer;
private $tempBuffer;
/**
* Create new Image object.
*
* @param string Original file path.
* @param boolean Init temporary buffer.
*/
public function __construct($filePath, $initTempBuffer = true)
{
$imageInfos = getimagesize($filePath);
if(in_array($imageInfos['mime'], array('image/jpeg',
'image/png',
'image/gif')))
{
$this->originalFilePath = $filePath;
$this->originalMimeType = $imageInfos['mime'];
$this->originalX = $imageInfos[0];
$this->originalY = $imageInfos[1];
if($initTempBuffer)
{
$this->initTempBuffer();
}
}
else
{
throw new md_Image_Exception('invalid image format.');
}
}
/**
* Init temporary buffer, copying original buffer.
* Try to get original buffer from file.
*/
public function initTempBuffer()
{
if(empty($this->originalBuffer))
{
$this->getImageFromFile();
}
$this->tempBuffer = $this->originalBuffer;
}
/**
* Resize Image, keeping scale.
*
* @param integer New size.
* @param string Reference side to resize ("x" or "y"), "x" by default.
*/
public function resizeKeepScale($new_value,$side="x")
{
if((int)$new_value > 0)
{
if($side=="x")
{
$factor = $this->originalX / (int)$new_value;
$new_y = round($this->originalY / $factor);
$this->resize((int)$new_value,$new_y);
}
if($side=="y")
{
$factor = $this->originalY / (int)$new_value;
$new_x = round($this->originalX / $factor);
$this->resize($new_x,(int)$new_value);
}
}
else
{
throw new md_Image_Exception('Can\'t resize image to 0 pixel !');
}
}
/**
* Resize image so that it would fit in a square of given pixel size.
*
* @param integer Size of square.
*/
public function fitToSquare($size)
{
if($this->originalX > $this->originalY)
{
$this->resizeKeepScale((int)$size);
}
else
{
$this->resizeKeepScale((int)$size,"y");
}
}
public function fitInSquare($size, $color = array(255, 255, 255))
{
if(count($color) < 3)
{
throw new md_Image_Exception('wrong color');
}
$this->fitToSquare($size);
$dst= imagecreatetruecolor($size, $size);
$color = imagecolorallocate($dst, (int)$color[0], (int)$color[1], (int)$color[2]);
imagefill($dst, 0, 0, $color);
$bufferX = imagesx($this->tempBuffer);
$bufferY = imagesy($this->tempBuffer);
if($bufferX > $bufferY)
{
$moveY = $size / 2 - $bufferY / 2;
$moveX = 0;
}
else
{
$moveY = 0;
$moveX = $size / 2 - $bufferX / 2;
}
imagecopy($dst, $this->tempBuffer, $moveX, $moveY, 0, 0, $bufferX, $bufferY);
$this->tempBuffer = $dst;
}
/**
* Resize image to new x and new y.
*
* @param integer New size of x.
* @param integer New size of y.
*/
public function resize($new_x,$new_y)
{
if(!empty($this->tempBuffer))
{
$to = imagecreatetruecolor($new_x, $new_y);
$from = $this->tempBuffer;
imagecopyresampled($to, $from, 0, 0, 0, 0, $new_x, $new_y,
$this->originalX, $this->originalY);
$this->tempBuffer = $to;
}
else
{
throw new md_Image_Exception('Can\'t resize empty image buffer');
}
}
/**
* Save temporary buffer to file.
*
* @param string New file path.
* @param boolean Add extension, based on original file.
*/
public function toFile($newPath, $addExtension = true)
{
if(!empty($this->tempBuffer))
{
if($addExtension)
{
$newPath .= '.' . $this->getOriginalExtension();
}
switch($this->originalMimeType)
{
case 'image/jpeg':
imagejpeg($this->tempBuffer,$newPath);
break;
case 'image/png':
imagepng($this->tempBuffer,$newPath);
break;
case 'image/gif':
imagegif($this->tempBuffer,$newPath);
break;
}
}
else
{
throw new md_Image_Exception('Can not save empty buffer.');
}
}
/**
* Send buffer to browser
*/
public function toBrowser($die = true)
{
header('content-type: ' . $this->originalMimeType);
switch($this->originalMimeType)
{
case 'image/jpeg':
imagejpeg($this->tempBuffer);
break;
case 'image/png':
imagepng($this->tempBuffer);
break;
case 'image/gif':
imagegif($this->tempBuffer);
break;
}
if($die)
{
die;
}
}
/**
* Convert image to gray scale.
*/
public function grayScale()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_GRAYSCALE);
}
}
/**
* Colorize image.
*
* @param integer Red value (0-255).
* @param integer Blue value (0-255).
* @param integer Green value (0-255).
* @param mixed Alpha channel.
*/
public function colorize($r, $b, $g, $alpha = null)
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_COLORIZE,
(int)$r,
(int)$b,
(int)$g,
$alpha);
}
}
/**
* Negates colors.
*/
public function negate()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_NEGATE);
}
}
/**
* Adjust brightness.
*
* @param integer brightness value
*/
public function brightness($val)
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_BRIGHTNESS, (int)$val);
}
}
/**
* Adjust contrast.
*
* @param integer contrast value
*/
public function contrast($val)
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_CONTRAST, (int)$val);
}
}
/**
* Edge effect
*/
public function edgeDetect()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_EDGEDETECT);
}
}
/**
* Emboss effect
*/
public function emboss()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_EMBOSS);
}
}
/**
* Gaussian blur
*/
public function gaussianBlur()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_GAUSSIAN_BLUR);
}
}
/**
* Selective blur
*/
public function selectiveBlur()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_SELECTIVE_BLUR);
}
}
/**
* Mean removal
*/
public function meanRemoval()
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_MEAN_REMOVAL);
}
}
/**
* Smooth effect
*/
public function smooth($val)
{
if(!empty($this->tempBuffer))
{
imagefilter($this->tempBuffer, IMG_FILTER_SMOOTH, (int)$val);
}
}
/**
* Create "Apple-like" reflection effect
*/
public function reflection()
{
$src_img = $this->tempBuffer;
$src_height = imagesy($src_img);
$src_width = imagesx($src_img);
$dest_height = $src_height + ($src_height / 2);
$dest_width = $src_width;
$reflected = imagecreatetruecolor($dest_width, $dest_height);
imagealphablending($reflected, false);
imagesavealpha($reflected, true);
imagecopy($reflected, $src_img, 0, 0, 0, 0, $src_width, $src_height);
$reflection_height = $src_height / 2;
$alpha_step = 80 / $reflection_height;
for ($y = 1; $y <= $reflection_height; $y++)
{
for ($x = 0; $x < $dest_width; $x++)
{
// copy pixel from x / $src_height - y to x / $src_height + y
$rgba = imagecolorat($src_img, $x, $src_height - $y);
$alpha = ($rgba & 0x7F000000) >> 24;
$alpha = max($alpha, 47 + ($y * $alpha_step));
$rgba = imagecolorsforindex($src_img, $rgba);
$rgba = imagecolorallocatealpha($reflected, $rgba['red'], $rgba['green'], $rgba['blue'], $alpha);
imagesetpixel($reflected, $x, $src_height + $y - 1, $rgba);
}
}
$this->tempBuffer = $reflected;
}
public function logo($src_image, $src_w, $src_h, $position='random')
{
$dst_w = imagesx($this->tempBuffer);
$dst_h = imagesy($this->tempBuffer);
imagealphablending($this->tempBuffer,true);
imagealphablending($src_image,true);
if ($position == 'random')
{
$position = rand(1,8);
}
switch ($position)
{
case 'top-right':
case 'right-top':
case 1:
imagecopy($this->tempBuffer, $src_image, ($dst_w-$src_w), 0, 0, 0, $src_w, $src_h);
break;
case 'top-left':
case 'left-top':
case 2:
imagecopy($this->tempBuffer, $src_image, 0, 0, 0, 0, $src_w, $src_h);
break;
case 'bottom-right':
case 'right-bottom':
case 3:
imagecopy($this->tempBuffer, $src_image, ($dst_w-$src_w), ($dst_h-$src_h), 0, 0, $src_w, $src_h);
break;
case 'bottom-left':
case 'left-bottom':
case 4:
imagecopy($this->tempBuffer, $src_image, 0 , ($dst_h-$src_h), 0, 0, $src_w, $src_h);
break;
case 'center':
case 5:
imagecopy($this->tempBuffer, $src_image, (($dst_w/2)-($src_w/2)), (($dst_h/2)-($src_h/2)), 0, 0, $src_w, $src_h);
break;
case 'top':
case 6:
imagecopy($this->tempBuffer, $src_image, (($dst_w/2)-($src_w/2)), 0, 0, 0, $src_w, $src_h);
break;
case 'bottom':
case 7:
imagecopy($this->tempBuffer, $src_image, (($dst_w/2)-($src_w/2)), ($dst_h-$src_h), 0, 0, $src_w, $src_h);
break;
case 'left':
case 8:
imagecopy($this->tempBuffer, $src_image, 0, (($dst_h/2)-($src_h/2)), 0, 0, $src_w, $src_h);
break;
case 'right':
case 9:
imagecopy($this->tempBuffer, $src_image, ($dst_w-$src_w), (($dst_h/2)-($src_h/2)), 0, 0, $src_w, $src_h);
break;
}
}
private function getImageFromFile()
{
switch($this->originalMimeType)
{
case 'image/jpeg':
$this->originalBuffer = imagecreatefromjpeg($this->originalFilePath);
break;
case 'image/png':
$this->originalBuffer = imagecreatefrompng($this->originalFilePath);
break;
case 'image/gif':
$this->originalBuffer = imagecreatefromgif($this->originalFilePath);
break;
}
}
private function getOriginalExtension()
{
switch($this->originalMimeType)
{
case 'image/jpeg':
return 'jpg';
break;
case 'image/png':
return 'png';
break;
case 'image/gif':
return 'gif';
break;
default:
return '';
break;
}
}
}
Suggestion d’utilisation:
require('md/Image.php');
$i = new md_Image('image.png');
$i->fitToSquare(200);
$i->toBrowser();
Un cache disque pour doctrine
A moins que je ne sois passé à coté de quelque chose, Doctrine n’est pas fourni en standard avec un driver disque pour son cache. On peut comprendre, dans la mesure où un cache APC ou Memcached est forcément plus rapide. Pour autant on n’a pas forcément accès à ces caches sur tous les hébergements. Doctrine permet également la mise en cache dans une base de donnée (SQLite par exemple) mais là encore les extensions nécessaires ne sont pas forcément chargées.
Voilà de quoi y remédier en utilisant un cache disque.
D’abord une classe pour mettre en cache sur le disque:
class md_Cache
{
private $cacheDir;
public function __construct($cacheDir, $checkDir = true)
{
$slash = substr($cacheDir,-1);
if($slash != PATH_SEPARATOR)
{
$cacheDir .= PATH_SEPARATOR;
}
if($checkDir)
{
if(!is_dir($cacheDir))
{
if(!mkdir($cacheDir, 0755, true))
{
throw new Exception('directory "'
. $cacheDir
. '" does ot exist and could not be created.');
}
}
}
$this->cacheDir = $cacheDir;
}
public function fetch($key)
{
$paths = $this->mkPaths($key);
if(!is_file($paths['meta']))
{
return false;
}
$metas = unserialize(file_get_contents($paths['meta']));
if($metas['expires'] < time() and $metas['expires'] != 0)
{
unlink($paths['meta']);
unlink($paths['cache']);
return false;
}
return unserialize(file_get_contents($paths['cache']));
}
public function store($key, $value, $ttl = 0)
{
$paths = $this->mkPaths($key);
if($ttl == 0)
{
$expires = 0;
}
else
{
$expires = time() + (int)$ttl;
}
file_put_contents($paths['cache'], serialize($value));
file_put_contents($paths['meta'], serialize(array(
'key' => $key,
'expires' => $expires,
'file' => basename($paths['cache'])
)));
return $value;
}
public function delete($key)
{
$paths = $this->mkPaths($key);
if(unlink($paths['meta']) and unlink($paths['cache']))
{
return true;
}
return false;
}
private function mkPaths($key)
{
$hash = md5($key);
return array(
'meta' => $this->cacheDir . $hash . '.meta',
'cache' => $this->cacheDir . $hash . '.cache'
);
}
public static function cacheInfos()
{
$metaFiles = glob($this->cacheDir . '*.meta');
$metas = array();
foreach($metaFiles as $metaFile)
{
$metas[] = unserialize(file_get_contents($metaFile));
}
return $metas;
}
public static function clear($removeDirectory = true)
{
$infos = $this->cacheInfos();
foreach($infos as $info)
{
$this->delete($info['key']);
}
if($removeDirectory)
{
return rmdir($this->cacheDir);
}
return true;
}
}
Ensuite le driver pour Doctrine:
class md_Cache_Doctrine_Disk extends Doctrine_Cache_Driver
{
private $mdCache;
public function __construct($options = array())
{
if(!isset($options['cacheDir']))
{
throw new Exception('option cacheDir must be set.');
}
if(!isset($options['checkDir']))
{
$options['checkDir'] = true;
}
$this->mdCache = new md_Cache($options['cacheDir'], $options['checkDir']);
parent::__construct($options);
}
/**
* Test if a cache is available for the given id and (if yes) return it (false else).
*
* @param string $id cache id
* @param boolean $testCacheValidity if set to false, the cache validity won't be tested
* @return mixed The stored variable on success. FALSE on failure.
*/
public function fetch($id, $testCacheValidity = true)
{
return $this->mdCache->fetch($id);
}
/**
* Test if a cache is available or not (for the given id)
*
* @param string $id cache id
* @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
*/
public function contains($id)
{
return $this->mdCache->fetch($id) === false ? false : true;
}
/**
* Save some string datas into a cache record
*
* Note : $data is always saved as a string
*
* @param string $data data to cache
* @param string $id cache id
* @param int $lifeTime if != false, set a specific lifetime for this cache record (0 => infinite lifeTime)
* @return boolean true if no problem
*/
public function save($id, $data, $lifeTime = 0)
{
return (bool) $this->mdCache->store($id, $data, $lifeTime);
}
/**
* Remove a cache record
*
* @param string $id cache id
* @return boolean true if no problem
*/
public function delete($id)
{
return $this->mdCache->delete($id);
}
}
On peut ensuite ajouter le cache par défaut à Doctrine au moment du bootstrap:
Doctrine_Manager::getInstance()
->setAttribute(
Doctrine::ATTR_RESULT_CACHE,
new md_Cache_Doctrine_Disk
(
array('cacheDir' => CACHE_DOCTRINE)
)
);
Et finalement utiliser le cache dans les requêtes:
Doctrine_Query::create()
->from('User u')
->useResultCache(true)
->setResultCacheLifeSpan(3600)
->fetchArray();
Doctrine Event Listener
L’ORM Doctrine a un système d’évènements et ça c’est quand même très sympa et très utile… par exemple quand on utilise MySQL et qu’on a besoin d’une connexion en utf-8:
class ConnectListener extends Doctrine_EventListener
{
public function postConnect(Doctrine_Event $event)
{
$this->setNamesUtf8();
}
private function setNamesUtf8()
{
Doctrine_Manager::connection()->setCharset('utf8');
}
}
Doctrine_Manager::connection()->setListener(new ConnectListener());
Et voilà… une connexion qui passe toute seule en utf-8 quand on en a besoin.
Python wannabe
J’ai souvent fait l’article de python dans ces colonnes tant ce langage semble avoir été conçu pour faciliter la vie du développeur. Malheureusement, au quotidien on est parfois loin d’avoir accès à de tels outils (ah! Si PHP était comme python…).
Parmis les mille et un détails qui font de python un langage ami du développeur se trouve la possibilité d’exécuter du code si et seulement si le fichier source est exécuté directement:
if __name__ == "__main__":
# do something
Malheureusement rien de tel en PHP… mais comme souvent avec PHP on peut palier les faiblesses du design par des hacks issus d’esprits créatifs:
if(count(debug_backtrace()) == 0)
{
// do something
}
C’est moche mais ça marche.
Nostalgie
Il me semble que j’étais meilleur avant.
LastFM Scrobbling
il y a quelques temps de celà j’utilisais une classe du projet Ampache pour soumettre des morceaux de musique à LastFM… seulement après un changement de version du protocole de “scrobbling“, cette classe est devenue obsolète. Voilà de quoi la remplacer:
<?php
/**
* PHP Scrobbler
*
* This class lets you submit tracks to a lastfm account. Curl needed.
*
* Basic usage:
*
* require('md/Scrobbler.php');
* $scrobbler = new md_Scrobbler('lastfmUser', 'password');
* $scrobbler->add('Jerry Goldsmith', 'The space jockey', 'alien', 289);
* $scrobbler->submit();
*
* @author Mickael Desfrenes <desfrenes@gmail.com>
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @link www.desfrenes.com
*/
class md_Scrobbler_Exception extends Exception{}
class md_Scrobbler
{
// change this according to your client id and version
const clientId = 'tst';
const clientVer = '1.0';
const SCROBBLER_URL = 'http://post.audioscrobbler.com/?hs=true&p=1.2.1&c=<client-id>&v=<client-ver>&u=<user>&t=<timestamp>&a=<auth>';
// curl timeout
const TIMEOUT = 10;
// lastfm user
private $user;
// lastfm user password
private $password;
// scrobbler session id
private $sessionId = '';
// last handshake failure timestamp (0 = no failure)
private $handShakeFailure = 0;
// number of hard failures
private $submitFailures = 0;
// store tracks here
private $queue = array();
private $nowPlayingUrl;
private $submissionUrl;
/**
* New md_Scrobbler
*
* @param string LastFM login
* @param string LastFM password
*/
public function __construct($user, $password)
{
$this->user = $user;
$this->password = $password;
}
/**
* Add a track to the queue
*
* @param string artist name
* @param string track title
* @param string album title
* @param integer track length (seconds)
* @param integer track play timestamp
* @param integer track number
* @param string source type (see lastFM API docs)
* @param integer rating
* @param string music brain track ID
* @return boolean
*/
public function add($artist, $track, $album = '', $trackDuration, $scrobbleTime = '', $trackNumber = '', $source = 'P', $rating = '', $mbTrackId)
{
if(empty($scrobbleTime))
{
$scrobbleTime = time();
}
$this->queue[] = array('artist' => $artist,
'track' => $track,
'scrobbleTime' => $scrobbleTime,
'trackDuration' => $trackDuration,
'album' => $album,
'trackNumber' => $trackNumber,
'source' => $source,
'rating' => $rating,
'mbTrackId' => $mbTrackId
);
return true;
}
/**
* Submission process
*
* @throws md_Scrobbler_Exception
* @return boolean
*/
public function submit()
{
if(empty($this->queue))
{
throw new md_Scrobbler_Exception('Nothing to submit.');
return false;
}
if(empty($this->sessionId) or $this->submitFailures > 2)
{
$this->handShake();
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_TIMEOUT, self::TIMEOUT);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, self::TIMEOUT);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_URL, $this->submissionUrl);
curl_setopt($curl, CURLOPT_POSTFIELDS, $this->generatePostData());
$data = curl_exec($curl);
curl_close ($curl);
$data = explode("\n", $data);
if($data[0] != 'OK')
{
$this->submitFailures++;
if($data[0] == 'BADSESSION')
{
throw new md_Scrobbler_Exception('Bad session id.');
}
else
{
throw new md_Scrobbler_Exception('Submission failed : ' . $data[0]);
}
return false;
}
else
{
$this->queue = array();
return true;
}
return false;
}
private function handShake()
{
if(empty($this->user) or empty($this->password))
{
throw new md_Scrobbler_Exception('Authentification credentials missing.');
return false;
}
$curl = curl_init($this->generateScrobblerUrl());
curl_setopt($curl, CURLOPT_TIMEOUT, self::TIMEOUT);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, self::TIMEOUT);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
$data = curl_exec($curl);
curl_close($curl);
$data = explode("\n", $data);
if($data[0] != 'OK')
{
$this->handShakeFailure = time();
switch($data[0])
{
case 'BANNED':
throw new md_Scrobbler_Exception('Client banned.');
break;
case 'BADTIME':
throw new md_Scrobbler_Exception('Wrong system clock.');
break;
case 'BADAUTH':
throw new md_Scrobbler_Exception('Wrong credentials.');
break;
default:
throw new md_Scrobbler_Exception('Unexpected handshake error: ' . $data[0]);
break;
}
return false;
}
else
{
$this->sessionId = trim($data[1]);
$this->nowPlayingUrl = trim($data[2]);
$this->submissionUrl = trim($data[3]);
$this->handShakeFailure = 0;
$this->submitFailures = 0;
return true;
}
return false;
}
private function generateScrobblerUrl()
{
$stamp = time();
return str_replace(
array('<client-id>',
'<client-ver>',
'<user>',
'<timestamp>',
'<auth>'),
array(self::clientId,
self::clientVer,
$this->user,
$stamp,
md5(md5($this->password) . $stamp)),
self::SCROBBLER_URL
);
}
private function generatePostData()
{
$body = 's=' . $this->sessionId . '&';
$i = 0;
foreach($this->queue as $item)
{
$body .= 'a[' . $i . ']=' . urlencode($item['artist']) . '&'
. 't[' . $i . ']=' . urlencode($item['track']) . '&'
. 'i[' . $i . ']=' . $item['scrobbleTime'] . '&'
. 'o[' . $i . ']=' . $item['source'] . '&'
. 'r[' . $i . ']=' . $item['rating'] . '&'
. 'l[' . $i . ']=' . $item['trackDuration'] . '&'
. 'b[' . $i . ']=' . urlencode($item['album']) . '&'
. 'n[' . $i . ']=' . $item['trackNumber'] . '&'
. 'm[' . $i . ']=' . $item['mbTrackId'] . '&';
$i++;
}
return $body;
}
}
Pour suivre les mises à jour: svn://sd-14043.dedibox.fr/md
Les suggestions Google
Il y a une fonctionnalitée sympa chez Google… les suggestions :

Surtout… ne me demandez pas pourquoi.
PHP5 et type hinting
PHP permet le “type hinting” mais uniquement pour les objets et les tableaux. Voici une bouine trouvée sur php.net pour ajouter à cette fonctionnalité les types scalaires, en attendant une hypothétique modification de PHP en ce sens :
'is_bool',
'integer' => 'is_int',
'float' => 'is_float',
'string' => 'is_string',
'resource' => 'is_resource'
);
public static function initializeHandler()
{
set_error_handler('Typehint::handleTypehint');
return TRUE;
}
private static function getTypehintedArgument($ThBackTrace, $ThFunction, $ThArgIndex, &$ThArgValue)
{
foreach ($ThBackTrace as $ThTrace)
{
if (isset($ThTrace['function']) && $ThTrace['function'] == $ThFunction)
{
$ThArgValue = $ThTrace['args'][$ThArgIndex - 1];
return TRUE;
}
}
return FALSE;
}
public static function handleTypehint($ErrLevel, $ErrMessage)
{
if ($ErrLevel == E_RECOVERABLE_ERROR)
{
if (preg_match(TYPEHINT_PCRE, $ErrMessage, $ErrMatches))
{
list($ErrMatch, $ThArgIndex, $ThClass, $ThFunction, $ThHint, $ThType) = $ErrMatches;
if (isset(self::$Typehints[$ThHint]))
{
$ThBacktrace = debug_backtrace();
$ThArgValue = NULL;
if (self::getTypehintedArgument($ThBacktrace, $ThFunction, $ThArgIndex, $ThArgValue))
{
if (call_user_func(self::$Typehints[$ThHint], $ThArgValue))
{
return TRUE;
}
}
}
}
}
return FALSE;
}
}
Typehint::initializeHandler();
function testInt(integer $int)
{
return $int;
}
function testString(string $string)
{
return $string;
}
testInt(5454);
testString('');
Consommez bien, on s’occupe de tout.
Je suis content de savoir que le gouvernement veille sur nous. Je suis vraiment rassuré. Il s’agit bien sur de “protéger les utilisateurs d’Internet”, alors forcément ça ne peut pas être mal.
Je pense qu’il est temps de revoir certains classiques