Autor wpisu: Diabl0, dodany: 29.06.2011 15:37, tagi: mysql, php, sql, zend_framework
Od jakiegoś czasu borykałem się z problemem dlaczego czasami zapisywane do bazy danych zserializowane dane są „obcinane. Poszukiwania, googlanie niewiele pomagało aż a końcu trafiłem gdzieś na informację że problemem może być znak NULL (0×00) w którym PHPowe serialize się lubuje . A koro tak, to postanowiłem napisać swoją wersję serialize.
Kod tutaj:
<?php /** * Serializowanie i deserializowanie treści zawierających 0x00 * * @category Mao * @package Mao_Serializer * @author Krzysztof 'Diabl0' Szatanik * @copyright Copyright (c) 2011, MAO Group * @version $Id: Form.php 1164 2009-11-10 13:01:53Z diabl0 $ */ /** * Własna nakładka na serializację * */ class Mao_Serializer { const CHAR_0x00 = '\\$0x00$/'; /** * Serializuje obiekt jak serialize() z tym że zastępuje znak 0x00 * aby nie sprawiał problemu przy zapisie do bazy danych * * * @param mixed $obj * * @return string */ static function serialize( $obj ) { return str_replace( chr(0x00), self::CHAR_0x00, serialize( $obj ) ); } /** * * Deserializuje obiekt jak unserialize() z tym że przywraca * zastąpiony znak 0x00 * * @param string $string * * @return mixed */ static function unserialize( $string ) { return unserialize( str_replace( self::CHAR_0x00, chr(0x00), $string ) ); } }
Użycie jest banalne:
<?php $text = 'to jest test znaku [' . chr(0) . '] - czy sie dobrze zapisuje do bazy'; $serialized = Mao_Serializer::serialize($text); $unserialized = Mao_Serializer::unserialize($serialized); ?>
Wiem że nie jest to idealne rozwiązanie ale na razie nie mam czasu aby przysiąść i dorobić jakąś konwersję w locie na poziomie Zend_Db_Adapter
Autor wpisu: Tomasz Kowalczyk, dodany: 19.06.2011 16:04, tagi: javascript, jquery, mysql, php
Autor wpisu: singles, dodany: 10.05.2011 19:45, tagi: mysql
MySQL i porównywanie ciagów z zachowaniem wielkości znaków
Zdarza się, że podczas programowania aplikacji internetowej opartej na MySQL potrzebujemy porównania z zachowaniem wielkości znaków, gdyż domyślnie porównanie wykonane jest bez zwracania uwagi an wielkość liter. Wpis ten przybliży metody, w jaki sposób można tą funkcjonalność osiągnąć.
Wprowadzenie
Zachowanie takie wynika z tego, że ogromna większość tworzonych tabel posiada ustawiony tryb porównania z końcówką ci
– co znaczy nie mniej nie więcej case insensitive. Kiedy wylistujemy dostępne typy porównań za pomocą polecenia SHOW COLLATION;
, otrzymamy listę bez przyrostków cs
, czyli case sensitive. MySQL udostępnia kilka porównań tego typu. Wpisanie polecenia:
SHOW COLLATION LIKE '%_cs';
zwróci następujące rekordy:
+--------------------+---------+----+---------+----------+---------+ | Collation | Charset | Id | Default | Compiled | Sortlen | +--------------------+---------+----+---------+----------+---------+ | latin1_general_cs | latin1 | 49 | | Yes | 1 | | latin2_czech_cs | latin2 | 2 | | Yes | 4 | | cp1250_czech_cs | cp1250 | 34 | | Yes | 2 | | latin7_estonian_cs | latin7 | 20 | | Yes | 1 | | latin7_general_cs | latin7 | 42 | | Yes | 1 | | cp1251_general_cs | cp1251 | 52 | | Yes | 1 | +--------------------+---------+----+---------+----------+---------+ 6 rows in set (0,00 sec)
Jak widać, nie ma tutaj żadnego typu, którego moglibyśmy użyć w polskojęzycznych aplikacjach. Dlatego musimy sobie poradzić inaczej – poniżej przedstawiam kilka sposobów.
Użyjemy prostej tabeli:
CREATE TABLEdemo
(id
int(11) NOT NULL AUTO_INCREMENT,name
varchar(100) DEFAULT NULL, PRIMARY KEY (id
) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Do tabeli dodane zostały dwa rekordy:
mysql> select * from demo; +----+------+ | id | name | +----+------+ | 1 | Foo | | 2 | FOO | | 3 | Bar | +----+------+ 3 rows in set (0,00 sec)
Autor wpisu: Tomasz Kowalczyk, dodany: 08.05.2011 19:40, tagi: php, mysql, framework, symfony2
Autor wpisu: Śpiechu, dodany: 07.05.2011 12:56, tagi: php, mysql
Dzisiaj kończę temat wstrzykiwania zależności. Punktem wyjścia będzie poprzedni kod. Kontener posłuży do skonfigurowania jeszcze dwóch obiektów. Napisałem sobie 2 proste klasy: SelectQuery
, który odpyta bazę danych na podane zapytanie oraz SelectQueryCache
, który zachowa wynik zapytania do pliku. Wynik będzie ważny tylko przez podany czas.
Przy okazji zmieniłem trochę formatowanie składni na bardziej „Zend-Frameworkowe”. Mam nadzieję, że bardziej czytelne. Kolejną zmianą jest używanie w nazwach klas/zmiennych wyłączenie języka angielskiego. Wam to nie będzie przeszkadzać, a mnie oszczędzi masę czasu gdybym chciał coś kiedyś rzucić na szerokie wody.
W metodzie loadDefaults()
dorzucamy zmienne konfigurujące obiekt cachujący i dwa domknięcia potrafiące wyprodukować gotowe obiekty:
//zmienne konfigurujące pdo z poprzedniego wpisu $this->cache_filename = 'queries.cache'; $this->cache_interval = 30; $this->cache_class = 'SelectQueryCache'; $this->selectQueryCache = function (DBKontener $k) { return new $k->cache_class($k->cache_filename , k->cache_interval); }; $this->selectQuery = function (DBKontener $k) { $q = new SelectQuery($k->pdo_getPDO); // wstrzykujemy zaleznosc $q->setCache($k->selectQueryCache); return $q; };
Właściwie to wpis można by teraz zamknąć. Widać jak cache tworzony jest na podstawie zmiennych oraz widać wstrzyknięcie zależności w postaci metody setCache()
. Innym sposobem wstrzykiwania jest konstruktor. Nie zrobiłem tak z uwagi na to, że SelectQuery
może sobie radzić bez obiektu cachującego, więc po co wymuszać.
Poniżej załączam kod dwóch wspomnianych wyżej klas.
class SelectQuery { /** * @var PDO */ protected $pdo; /** * @var SelectQueryCache */ protected $cache = null; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * @param SelectQueryCache $sqc dependency injection */ public function setCache(SelectQueryCache $sqc) { $this->cache = $sqc; } /** * @param string $query * @return array */ public function getResults($query) { if ($this->cache) { $cachedResults = $this->cache->getCachedResults($query); if ($cachedResults !== null) { return $cachedResults; } } $q = $this->pdo->prepare($query); $q->execute(); $rows = array(); while ($row = $q->fetch(PDO::FETCH_ASSOC)) { $rows[] = $row; } if ($this->cache) { $this->cache->cacheQuery($query , $rows); } return $rows; } } class SelectQueryCache { /** * @var array contains all cached queries */ protected $cachedQueries = array(); /** * @var SPLFileInfo */ protected $cacheFile; /** * @var int time interval in secs to check if cache is still valid */ protected $validTime; public function __construct($filename , $validtime = 60) { $this->cacheFile = new SplFileInfo(__DIR__ . '/' . $filename); $this->validTime = (int) $validtime; $this->loadCache(); } protected function loadCache() { if (!file_exists($this->cacheFile)) { touch($this->cacheFile); } $file = $this->cacheFile->openFile('r'); if ($file->flock(LOCK_SH)) { $this->cachedQueries = json_decode(file_get_contents($file) , true); $file->flock(LOCK_UN); } else { throw new Exception('Cannot acquire file lock to read file'); } } public function cacheQuery($query , $result) { $this->cachedQueries[$query] = array( 'time' => time() , 'result' => $result); } public function getCachedResults($query) { if ($this->isValidCache($query)) { return $this->cachedQueries[$query]['result']; } else { return null; } } protected function isValidCache($query) { if ($this->cachedQueries !== null && array_key_exists($query , $this->cachedQueries) && $this->isValidTime($this->cachedQueries[$query]['time'])) { return true; } else { return false; } } protected function isValidTime($timestamp) { return ((time() - $this->validTime) < $timestamp); } protected function cleanUpOldCache() { foreach ($this->cachedQueries as $key => $cq) { if (!$this->isValidTime($cq['time'])) { unset($this->cachedQueries[$key]); } } } protected function save() { $serializedData = json_encode($this->cachedQueries); $file = $this->cacheFile->openFile('w'); if ($file->flock(LOCK_EX)) { $file->fwrite($serializedData); $file->flock(LOCK_UN); } else { throw new Exception('Cannot acquire exclusive file lock to save file'); } } public function __destruct() { $this->cleanUpOldCache(); $this->save(); } }
Myślę, że w SelectQuery
nie ma nic specjalnego, no może poza getResults()
, które najpierw sprawdza czy jest cache i jakiś wynik, a jak nie to odpytuje bazę danych i zapisuje wynik do cache.
Za to w SelectQueryCache
znajdzie się trochę mięska:
- Do serializacji danych używam
json_encode
, które jest ponoć trochę szybsze od zwykłej serializacji, a zapisane dane mają formę strawniejszą dla humanoidów. - Dane w pliku trzymam w postaci tablicy asocjacyjnej, której kluczem jest zapytanie, a wartościami wynik zapytania i czas utworzenia.
- Do sprawdzania czy cache nie jest przeterminowany odejmuję liczbę sekund od obecnego czasu i sprawdzam czy jest wcześniejsza od czasu utworzenia danego klucza.
- Oczyszczam cache sprawdzając czy poszczególne klucze nie są już przeterminowane i w razie czego wywalam cały klucz.
- Do zapisu do pliku używam destruktora. Mam pewność, że obiekt zaraz przed zakończeniem żywota zapisze dane do pliku.
Na koniec przykład użycia całości:
$c = new DBKontener(); $query = $c->selectQuery; $results = $query->getResults('SELECT nazwa_pola FROM jakas_tabela');
Za pierwszym razem dane zostaną pobrane z bazy, a następnie przez 30 sekund z pliku cache.