Float vs. setlocale()

Mark Story wpadł na ciekawy problem, który opisał na swoim blogu.

W dużym skrócie, jeśli używasz setlocale() to rzutowanie typów danych może działać nieprzewidywalnie. Polecam wpis Marka. Jednak grzebiąc nieco po php.net okazało się, że jest jeden sposób na zmianę typu, która bierze pod uwagę locale:

$pi = 3.141593;
echo (float)(string)$pi ."\n";
var_dump(setlocale(LC_NUMERIC, 'pl_PL.utf8'));
$polishPi = 3.141593;
// poniżej "problematyczne" rzutowanie
echo "string: " . (string)$polishPi ."\n";
echo "float: " . (float)(string)$polishPi ."\n";
echo "floatval(): " . floatval((string)$polishPi) ."\n";
echo "double: " . (double)(string)$polishPi ."\n";
echo "real: " . (real)(string)$polishPi ."\n";

//settype wydaje się działać dobrze z setlocale
var_dump(settype($polishPi, "double")); // można użyć "float"
echo "settype(): " . $polishPi ."\n";
var_dump(gettype($polishPi));
var_dump($polishPi*2);
var_dump(gettype($polishPi*2));

out:

3.141593
string 'pl_PL.utf8' (length=10)

string: 3,141593
float: 3
floatval(): 3
double: 3
real: 3

boolean true

settype(): 3,141593

string 'double' (length=6)

float 6,283186
string 'double' (length=6)
Share Button

Lazy Loading w modelach a’la CakePHP

CakePHP nie wspiera aktualnie leniwego ładowania obiektów. W prawdzie są jakieś skrypty, ale jeszcze żaden z nich nie chciał mi zadziałać. Zacząłem się zastanawiać: “Gdybym miał zaimplementować Lazy Loading w warstwie modelu, to jak bym to zrobił?” To co mi wyszło korzysta z rozwiązań PHP5, więc może wsparcie gałęzi 1.x Cake’a dla PHP4 implikuje brak tego wzorca?

Tak czy siak, podzielę się z Wami moimi pomysłami.

Zacznijmy od początków. Przeciwieństwem leniwego ładowania jest ładowanie gorliwe (lub zachłanne), to znaczy ładowanie na zapas wszystkiego co może okazać się potrzebne. Warstwa modelu w cake’u mogła by wyglądać tak (duże uproszeczenie, jedyna możliwa relacja to belongsTo).

Eager Loading


class Model {
	function find(){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."`";

		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."
"); if(isset($this->belongsTo) && is_array($this->belongsTo)){ foreach($this->belongsTo as $related){ $this->{$related} = new $related(); } } } } class User extends Model{ protected $belongsTo = array("Group"); } class Group extends Model{ } $user = new User(); $user->find(); echo "
"; $user->Group->find();

Widzimy, że obiekt Group jest inicjowany długo przed użyciem:

creating User
creating Group
calling User::find()

string 'Select * from `users` as `User`' (length=31)


calling Group::find()

string 'Select * from `groups` as `Group`' (length=33)

Mało tego, ładuje się nawet jeśli nigdy nie wywołamy metody w tym obiekcie (zakomentuj $user->Group->find(); żeby się o tym przekonać).

Lazy Loading

class Model {
	function find(){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."`";

		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."
"); } function __get($property){ if(!isset($this->{$property})) { $this->{$property} = new $property(); } if(in_array($property, $this->belongsTo)){ return $this->{$property}; }else{ $r = new ReflectionClass($this); throw new Exception( "$property not related with {$r->getName()}" ); } } } class User extends Model{ protected $belongsTo = array("Group"); } class Group extends Model{ } $user = new User(); $user->find(); echo "
"; $user->Group->find();

W tym rozwiązaniu obiekt Group nie zostanie stworzony, dopóki nie będziemy go potrzebować. Wykorzystana jest do tego magiczna metoda __get, która jest wywoływana wtedy, gdy próbujemy się odnieść do atrybutu obiektu, który nie istnieje.

ale pozostaje kwestia wyszukiwania w modelu tak, żeby zwrócił powiązane dane
np.: var_dump($user->find());

array
  0 => 
    array
      'User' => 
        array
          'id' => int 1
          'name' => string 'Franek' (length=6)
      'Group' => 
        array
          'id' => int 1
          'name' => string 'Admin' (length=5)
  1 => 
    array
      'User' => 
        array
          'id' => int 2
          'name' => string 'Janek' (length=5)
      'Group' => 
        array
          'id' => int 10
          'name' => string 'User' (length=4)

Wiemy, że belongsTo oznacza, że User powinien mieć pole (group_id), zatem query powinno wyglądać mniej więcej tak:

Select User.id, User.name, Group.id, Group.name from users as User
Inner Join groups as Group on (User.group_id = Group.id)

W tej chwili cakephp zawiera behavior containable, jednak on polega na załadowanych już modelach.

Zostańmy przy terminologii cake’a i przyjmijmy, że będziemy przekazywać informacje na temat powiązanych elementów w zmiennej $contain

function find($contain=null)

Teraz chciałbym powiedzieć: obiekcie $user zwróć mi wszystkich użytkowników z bazy wraz z informacją w jakich są grupach:

$user =  new User();

$user->find(
	array(
		"belongsTo" => array($user->Group)
	)
);

Zatem zaktualizuję metodę find:

	function find($contain=null){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "Select * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."` \n";


		if(!empty($contain) && is_array($contain["belongsTo"])){
			foreach($contain["belongsTo"] as $belongsToModel){
				$query .= $belongsToModel->getBelongsToJoin($this);
			}
		}


		var_dump($query);

		//return query result
	}

która od teraz zakłada, że model posiada metodę, która potrafi utworzyć odpowiedniego joina:

	function getBelongsToJoin($obj){
		$owner_reflection = new ReflectionObject($this);
		$obj_reflection = new ReflectionObject($obj);
		return "INNER JOIN `".strtolower($owner_reflection->getName())."s` 
					as `".$owner_reflection->getName()."`
						ON (`".$owner_reflection->getName()."`.`id` = `".$obj_reflection->getName()."`.`".strtolower($owner_reflection->getName())."_id`)
					";
	}

to jest oczywiście bardzo brzydkie ze względu na refleksje. Metodę getName() można jednak zaimplementować w klasie Model.

Tak czy siak, w tym wypadku nawet w find()’zie uwzględniającym relacje można stosować Lazy Loading

Na koniec całość kodu, z większą ilością relacji i przykładem działania (i małym refactoringiem – skoro już mowa o lazy loading to głupio było ładować piętnaście razy ReflectionClass):


class ModelException extends Exception{}

class Model {

	protected $reflection = null;

	function getName(){
		if(is_null($this->reflection)){
			$this->reflection= new ReflectionClass($this);
		}
		return $this->reflection->getName();
	}

	function getTableName(){
		return strtolower( $this->getName() . "s" );
	}

	function getFKName(){
		return strtolower( $this->getName() . "_id");
	}

	function getBelongsToJoin($obj){
		$owner_reflection = new ReflectionObject($this);
		$obj_reflection = new ReflectionObject($obj);
		return "INNER JOIN `".$this->getTableName()."` 
					as `".$this->getName()."`
						ON (`".$this->getName()."`.`id` = `".$obj->getName()."`.`".$this->getFKName()."`)
					";
	}

	function find($contain=null){

		$r = new ReflectionClass($this);
		print "calling ".$r->getName()."::find($conditions)";

		$query = "\nSelect * from `".strtolower($r->getName())."s` ".
					"as `".$r->getName()."` \n";


		if(!empty($contain) && is_array($contain["belongsTo"])){
			foreach($contain["belongsTo"] as $belongsToModel){
				$query .= $belongsToModel->getBelongsToJoin($this);
			}
		}


		var_dump($query);

		//return query result
	}

	function __construct(){
		$r = new ReflectionClass($this);
		print("creating ".$r->getName()."
"); } function __get($property){ if(!isset($this->{$property})) { $this->{$property} = new $property(); } if(in_array($property, $this->belongsTo)){ return $this->{$property}; }else{ $r = new ReflectionClass($this); throw new ModelException( "$property not related with {$r->getName()}" ); } } } class User extends Model{ protected $belongsTo = array("Group", "Unit", "Company"); } class Group extends Model{ } class Unit extends Model{} class Company extends Model{} $user = new User(); $user->find( array( "belongsTo" => array($user->Unit, $user->Company) ) );
Share Button