Förslag till en samling tutorials

30 maj, 2011 av Daniel Liljeberg 1 kommentar »

Jag har funderat lite på vad man skulle kunna göra en tutorial/kurs om. Det finns ju otroligt mycket bra information på nätet redan och även om detta mest hade varit en kul liten grej så hade det varit roligt om någon lärde sig något på samma gång. Jag tror att jag kommer rikta in mig på en webapplikation av enklare slag skriven i PHP. Med enklare menar jag då, inte som omfattande. Vi skall fortfarande använda oss av abstrakta databaslager, AJAX och annat trevligt. Mycket då det saknas en del bra information för den svenska publiken om PHP men också för att det händer mycket med språket. Det möjliggör också för många att testa på det då alla delar vi kommer använda oss av kommer vara gratis (eller ha en free trial) utan att bli beroende av tredjepartslösningar som exempelvis Mono om man nu sitter i exempelvis Linux. Jag är dock inte partisk utan kan mycket väl se att vi i ett senare skede gör om exakt samam applikation i .Net för att se likheter och skillnader.

Jag tänkte försöka utgå ifrån att inte förvänta mig en massa kunskap av de som tar del av materialet. Men naturligtvis så kommer det att hjälpa om man utvecklat tidigare då jag endast kort kommer gå igenom grunder och sedan gå in på lite mera avancerade områden. Jag tänker mig följande upplägg

  1. Sätta upp lokala utvecklingsverktyg och saker att tänka på (OS, IDE, användbara plugins etc)
  2. Sätta upp server (utvecklingsstack, databas, versions hantering etc)
  3. Lite kort om PHP, karakteristiska saker med språket. Styrkor och svagheter etc.
  4. Ett antal enklare mindre program som visar lite på olika sätt att koda under PHP (och många andra språk också)
  5. Enklare introduktion till OOP och hur PHP hanterar detta.
  6. Introduktion till Zend Framework
  7. Introduktion av MVC mönstret

Sedan börjar vi knacka på vår lilla applikation och börjar då titta på hur Zend Framework hanterar views etc. Sedan går vi in på användarhantering, åtkomstkontroll, osv.

Detta är bara en idé om punkter att ta upp och jag tror att det hela, dynamisk, växer fram med tiden. Finns det några idéer eller önskemål så kom gärna med förslag.

flattr this!

apt-cyg, apt-get för Cygwin

25 maj, 2011 av Daniel Liljeberg 3 kommentarer »

Ni som använder Cygwin har kanske irriterat er på att behöva dra igång “Setup.exe” någon gång för att installera något litet program ni saknat. Då önskar man att något i stil med “apt-get” eller “yum” funnits för Cygwin.

“apt-cyg” är just detta. Syntaxen påminner en hel del om just “apt-get”.

  • "apt-cyg install <package names>" to install packages
  • "apt-cyg remove <package names>" to remove packages
  • "apt-cyg update" to update setup.ini
  • "apt-cyg show" to show installed packages
  • "apt-cyg find <pattern(s)>" to find packages matching patterns
  • "apt-cyg describe <pattern(s)>" to describe packages matching patterns
  • "apt-cyg packageof <commands or files>" to locate parent packages

Projektet finns på Google Code och kan således hämtas via SVN.

Installera

svn --force export http://apt-cyg.googlecode.com/svn/trunk/ /bin/
chmod +x /bin/apt-cyg

Använda

Nästa gång du sedan kommer på att du tex saknar “nano” så behöver 
du inte köra “Setup.exe” utan kan istället köra

apt-cyg install nano

flattr this!

Cygwin + Console2 = Linux terminal i Windows

25 maj, 2011 av Daniel Liljeberg 2 kommentarer »

Även om jag länge använt windows så har jag alltid saknat en användbar terminal. När man jobbat med linux och OSX så vänjer man sig fort vid hur snabbt det går att jobba i terminalerna på dessa OS.

cmd.exe

I Windows har vi länge haft “cmd.exe” att vända oss till när vi vill jobba I någon form av terminal.

image

“cmd.exe” har dock lämnat mycket att önska och speciellt för folk som arbetat I Linux och vant sig vid Linux terminal. I Linux är terminalen något kraftfullt medan det I Windows ofta ses som något nödvändigt ont.

Powershell

För att göra Windows shell lite mera användarvänlig så kom Microsoft med “Powershell”. Här närmade man sig Linux shell lite och erbjöd program med välkända namn för de som jobbat I Linux shell. Exempelvis fungerar “ls”, “grep” osv i Powershell. Dock så är syntaxen ofta lite annorlunda än vad man kanske är van vid. Men för den som saknat ett “fungerande” shell I Windows så var det ett steg I rätt riktning.

image

Cygwin

För att lösa avsaknaden av ett fungerade shell i Windows så brukar jag dock installera Cygwin. Om du är helt ny till Cygwin så skulle man kunna likna det vid att du kör Linux I ditt Windows. Nu är det inte riktigt så och det är ingen VM som ligger och snurrar eller någon form av emulation som fortgår. Istället är Cygwin är en uppsättning av välkända program ifrån Linux så som "“grep”, “ls”, “cat”, “tail” osv. som alla fungerar precis som i Linux. De är ofta kompilerade ifrån samma källkod. Du kan köra en bash shell och installera välkända komponenter ifrån Linux så som “Cron”, “OpenSSH”, “Apache” med mera. Det mesta av detta kan du installera även i Windows vanligtvis, men genom Cygwin kan du sedan jobba med dem som om du arbetade i en Linux installation.

image

Du kan även installera en X server i Cygwin och köra X spplikationer om du har behov av detta. Du kan då kompilera de flesta Linux applikationer ifrån källkod och köra dem under Cygwin.

Jag använder dock oftast Cygwin för att få en “fungerande” shell in Windows. Det enda som varit lite jobbigt med Cygwin har varit att det ser ut att använda sig av Windows vanliga konsol. Så samma begränsningar som finns i cmd.exe har också funnints i Cygwin. Du kan tex inte dra i ditt fönster för att göra det bredare än 80 tecken osv.

Console2

Men här kommer Console2 som en liten räddare i nöden. Console2 ger dig möjlighet att skala om ditt fönster, öppna flera konsoler i tabbar osv, osv. Om du sedan går in i Edit/Settings och väljer Console så kan du där välja Shell att använda. Låt oss nu säga att du installerat Cygwin i "C:\cygwin". Då kan du där skriva

"C:\cygwin\bin\bash.exe –login -i"

vilket nu gör att Console2 kommer köra Cygwins bash shell istället för Windows tråkiga konsol. Efter detta har man en terminal som i mångt och mycket fungerar precis som man vant sig vid ifrån Linux.

image

Så nu kan man med enkelhet sitta i Windows och administrera Linux burkar, svn repos osv utan att behöva påminnas om det faktum att man just sitter i Windows Winking smile

Alternativ till Console2

Alternativ är att installera “rxvt” via Cygwin. Den finns i en version som kräver X server och en som klarar sig utan.

image

Eller så kan man installera Terminator som är en cross platform terminal som på Windows kräver Cygwin och Cygwin Ruby.

image

Dock upplever jag Terminator som rätt seg att starta och att den ibland låser sig.

Mintty finns även det att installera via Cygwin och är ett värdigt alternativ.

image

Xterm kompatibel vilket gör att många kommer känna sig hemma, drag-and-drop stöd (vilket även Console2 har) osv. Dock tyvärr inget stöd för tabbar och en del interaktiva program kan ibland ha lite problem. (tackar för tippset Torbjörn)

flattr this!

Vilket språk skall man välja? :)

24 maj, 2011 av Daniel Liljeberg Inga kommentarer »

Funderar på att skapa lite video tutorials/kurser om jag får tid. Funderar på om jag skall göra dem på svenska eller engelska. Jag har medvetet valt att skriva på svenska här då det finns så mycket information att få på engelska, men då allas engelska inte är flytande så ville jag kunna erbjuda information även till dem. Men vad tycker ni, svenska eller engelska?

flattr this!

SQL IN() utan dynamisk SQL

16 maj, 2011 av Daniel Liljeberg Inga kommentarer »

Fick en fråga för ett tag sedan hur man kunde lösa ett problem som involverade en sql stored procedure innehållande en ‘IN’ vilkens data man skulle skicka in till proceduren. Detta var löst så som många löser det, med dynamisk SQL. Detta kan liknas vid att du bygger upp din SQL fråga av strängar i din SP och sedan exekverar queryn. Exempelvis

1 CREATE PROC foobar 2 3 @ids as nvarchar(500) 4 as 5 begin 6 declare @sql as varchar(500) 7 8 SET @sql='select foo 9 from bar 10 where foo_id in ('+@ids+')' 11 12 ... 13

‘ids’ är här en komma sepparerad lista av id’n som vi vill göra vårt uppslag emot. Den dynamiska SQL queryn är inte en del av vår SP utan körs i sitt eget scope. Nackdelen med denna approach är dock att det inte är så lätt för query optimizern att kunna luska ut hur din query bäst skall köras. Utöver detta så måste användare av vår SP nu också ha SELECT rättigheter på alla tabeller som vi i vår dynamiska query hämtar data ifrån. Detta behöver man inte för en SP då rättigheterna sätta på SP’n och det sedan inte spelar någon roll vilka tabeller vår SP hämtar data ifrån. Att dynamiskt generera sin query kan också lämna dörren öppen för SQL injections. Om du hamnar i en situation då du måste använda dynamisk SQL, försök att alltid exekvera dem med ‘sp_executesql’.

Ett sätt att behålla möjligheten att dynamiskt skicka data till din ‘IN’ men slippa dynamisk SQL är att skapa en funktion som splittar upp din komma separerade lista och spara ner värdena i en temporär tabell som du sedan gör ett uppslag emot.

En funktion för att splitta en sträng kan göras på många sätt. Nedan följer en variant som gör det möjligt att välja vilken character man vill splitta på.

1 CREATE FUNCTION dbo.Split(@String varchar(8000), 2 @Delimiter char(1)) 3 returns @temptable TABLE (items varchar(8000)) 4 as 5 begin 6 declare @idx int 7 declare @slice varchar(8000) 8 9 select @idx = 1 10 if len(@String)<1 or @String is null return 11 12 while @idx!= 0 13 begin 14 set @idx = charindex(@Delimiter,@String) 15 if @idx!=0 16 set @slice = left(@String,@idx - 1) 17 else 18 set @slice = @String 19 20 if(len(@slice)>0) 21 insert into @temptable(Items) values(@slice) 22 23 set @String = right(@String,len(@String) - @idx) 24 if len(@String) = 0 break 25 end 26 return 27 end

Nu kan du använda dig av denna funktion och splitta din sträng innehållande dina id’n.

1 CREATE PROC foobar 2 3 @ids as nvarchar(500) 4 as 5 begin 6 declare @sql as varchar(500) 7 8 SELECT foo 9 FROM bar 10 WHERE foo_id IN(SELECT CONVERT(int, items) 11 FROM dbo.Split(@ids, ',')) 12 13 ... 14

Dynamisk SQL är dock inte alltid av ondo och kan ibland vara precis rätt väg att gå. Så testa båda lösningarna och se vad som passar bäst för just ditt ändamål. Men håll koll på säkerhets detaljerna.

flattr this!

Abstrahera databaslogiken ytterligare en nivå

13 maj, 2011 av Daniel Liljeberg Inga kommentarer »

Hanteringen och kopplingen till datakällorna går ofta igenom en mognadsprocess ju mer en utvecklare lär sig. Det är inte ovanligt att man ser kod i stil med

1 function getUsername($userId) { 2 $query = " 3 SELECT * FROM users 4 WHERE id=%s 5 LIMIT 1 6 "; 7 $result = mysql_queryf($query, $userId); 8 while($row = mysql_fetch_array($result)) 9 { 10 $output = $row[username]; 11 12 } 13 return $output; 14 }

Många som jobbat lite med databasdrivna applikationer börjar förr eller senare att abstrahera bort databaslogiken. I ett första steg kan detta te sig som så att man skapar objekt som har fint namngivna funktioner, exempelvis ett User objekt med funktioner så som getName() osv. I dessa funktioner sker sedan databasanrop annat. Men det publika interfacet av objektet blir mycket trevligare.

1 class User 2 { 3 private $_userId; 4 5 ... 6 7 function getUsername() { 8 $query = " 9 SELECT * FROM users 10 WHERE id=%s 11 LIMIT 1 12 "; 13 $result = mysql_queryf($query, $this->_userId); 14 while($row = mysql_fetch_array($result)) 15 { 16 $output = $row[username]; 17 18 } 19 return $output; 20 } 21 }

Nästa steg är ofta att hämta ut all relevant data i konstruktorn av User och således inte hämta den varje gång du anropar getUsername(). Sedan bygger man vidare med cachning av datan osv, osv.

Jag satt själv och funderade på detta för ett tag sedan och tänkte att man egentligen bör abstrahera det så pass att User objektet i detta fallet inte har någonting med databasen att göra. Data bör kunna sparas på olika ställen utan att vår User egentligen skall behöva förändras. Så all logik kopplad till lagring av data bör således inte ligga i klassen som definierar vår User.

Istället bestämde jag mig för att göra som så att min modell, User i detta fallet, endast innehöll data, samt funktioner för att manipulera denna data. Att sedan hämta och lagra datan hanteras istället av en annan klass. Jag valde att kalla dessa för Gateways. På detta sätt kan man skapa Gateways som jobbar emot olika datakällor utan att behöva pilla på våra modell klasser. Detta lämpar sig tex om man sitter och utvecklar ny funktionalitet och vill ha testdata lokalt i en XML fil istället för att jobba mot databasen. När det sedan är dags att jobba emot databasen så använder man sig bara av en annan Gateway och så är det löst.

För att uppnå detta skapar vi en basklass för våra modeller. I denna basklass har vi funktioner för att fylla på data, samt att hämta ut data. Vi inför en standard som säger att data i våra datakällor sparas i formatet ‘foo_id’ och att vår modell i så fall kommer innehålla funktioner som heter ‘setFooId()’ och ‘getFooId()’.

1 <?php 2 abstract class Core_Model_Abstract 3 { 4 protected $_isSaved = false; 5 6 public function __construct(array $options = null){ 7 if(is_array($options)){ 8 $this->setOptions($options); 9 } 10 } 11 12 public function __set($name, $value){ 13 $methodName = 'set'; 14 $nameParts = explode('_', $name); 15 16 foreach($nameParts as $namePart) { 17 $methodName .= ucfirst($namePart); 18 } 19 20 if((!method_exists($this, $methodName)){ 21 throw new Exception('Invalid property ' .$methodName . ' for ' . get_called_class()); 22 } 23 $this->$methodName($value); 24 } 25 26 public function __get($name){ 27 $methodName = 'get'; 28 $nameParts = explode('_', $name); 29 30 foreach($nameParts as $namePart) { 31 $methodName .= ucfirst($namePart); 32 } 33 34 if(('gateway' == $name) || !method_exists($this, $methodName)){ 35 throw new Exception('Invalid property ' .$methodName . ' for ' . get_called_class()); 36 } 37 return $this->$methodName(); 38 } 39 40 public function setOptions(array $options){ 41 $methods = get_class_methods($this); 42 foreach($options as $name => $value){ 43 $methodName = 'set'; 44 $nameParts = explode('_', $name); 45 46 foreach($nameParts as $namePart) { 47 $methodName .= ucfirst($namePart); 48 } 49 50 if(in_array($methodName, $methods)){ 51 $this->$methodName($value); 52 } 53 else { 54 Core_Log::log('Method ' . $methodName . ' does not exist for ' . get_called_class(), Core_Log::INFO); 55 } 56 } 57 return $this; 58 } 59 60 public function setIsSaved($isSaved) { 61 $this->_isSaved = $isSaved; 62 } 63 64 public function getIsSaved() { 65 return $this->_isSaved; 66 } 67 }

En klass för en User hade då sett ut i stil med.

1 <?php 2 class Core_Model_User extends Core_Model_Abstract 3 { 4 private $_userId; 5 private $_username; 6 7 ... 8 9 public function getUserId() { 10 return $this->_userId; 11 } 12 13 public function setUserId($id) { 14 $this->_userId = $id; 15 } 16 17 public function getUsername() { 18 return $this->_username; 19 } 20 21 public function setUsername($_username) { 22 $this->_username = $_username; 23 } 24 }

 

Denna modell kan nu innehålla data, men hur skall vi då sköta lagring av våra modeller? Här kommer våra Gateways in. Även här börjar vi med en basklass med det som är gemensamt för alla våra Gateways. En specialanpassad gateway för att spara till DB kan sedan se ut enligt följande.

1 <?php 2 3 /** 4 * Gateway class between Models and Database 5 * @author danlil 6 * 7 */ 8 abstract class Core_Model_Gateway_Db extends Core_Model_Gateway_Abstract 9 { 10 protected $_dbTableClass; 11 protected $_modelClass; 12 protected $_dbTable; 13 14 public function setDbTable($dbTable){ 15 if(is_string($dbTable)){ 16 $dbTable = new $dbTable(); 17 } 18 if(!$dbTable instanceof Core_Db_Table_Abstract){ 19 throw new Exception('Invalid table data gateway provided'); 20 } 21 $this->_dbTable = $dbTable; 22 return $this; 23 } 24 25 public function getDbTable(){ 26 if(null === $this->_dbTable){ 27 $this->setDbTable($this->_dbTableClass); 28 } 29 return $this->_dbTable; 30 } 31 32 public function save(Core_Model_Abstract $model) { 33 $tableInfo = $this->getDbTable()->info(); 34 $pk = $this->getDbTable()->getPrimaryKey(); 35 $setPkFunction = 'set'; 36 $getPkFunction = 'get'; 37 38 if(is_array($pk)) { 39 throw new Exception("Support for multicolumn primary keys not yet implemented. Prehaps it's time!"); 40 } 41 42 /** 43 * As a standard we take column names like foo_bar and try to get 44 * the correct data to put in it by calling getFooBar() from the model 45 * object. 46 */ 47 // echo "<pre>"; 48 // die(var_dump($tableInfo['metadata'])); 49 foreach($tableInfo['metadata'] as $columnName => $columnMetadata) { 50 $methodName = 'get'; 51 $columnNameParts = explode('_', $columnName); 52 53 foreach($columnNameParts as $columnNamePart) { 54 $methodName .= ucfirst($columnNamePart); 55 56 if($columnName == $pk) { 57 $getPkFunction .= ucfirst($columnNamePart); 58 $setPkFunction .= ucfirst($columnNamePart); 59 } 60 } 61 62 if(!method_exists($model, $methodName)) { 63 $className = get_class($model); 64 Core_Log::log("Function \"$methodName\" does not exist in \"$className\". Either implement the function or override the save() function.", Core_Log::CRIT); 65 throw new Exception("Function \"$methodName\" does not exist in \"$className\". Either implement the function or override the save() function."); 66 } 67 68 $data[$columnName] = $model->$methodName(); 69 } 70 71 $id = $model->$getPkFunction(); 72 73 if($model->$getPkFunction() != null && $model->getIsSaved() == false) { 74 $lastInsertId = $this->getDbTable()->insert($data); 75 $model->$setPkFunction($lastInsertId); 76 } 77 else if(null === $id){ 78 unset($data[$pk]); 79 $lastInsertId = $this->getDbTable()->insert($data); 80 $model->$setPkFunction($lastInsertId); 81 } 82 else{ 83 unset($data[$pk]); 84 //$where = $this->getDbTable()->getAdapter()->quoteInto('bug_id = ?', 1234); 85 $this->getDbTable()->update($data, array("$pk = ?" => $id)); 86 } 87 } 88 89 public function delete(Core_Model_Abstract $model) { 90 $tableInfo = $this->getDbTable()->info(); 91 $pk = $this->getDbTable()->getPrimaryKey(); 92 $setPkFunction = 'set'; 93 $getPkFunction = 'get'; 94 95 if(is_array($pk)) { 96 throw new Exception("Support for multicolumn primary keys not yet implemented. Prehaps it's time!"); 97 } 98 99 /** 100 * As a standard we take column names like foo_bar and try to get 101 * the correct data to put in it by calling getFooBar() from the model 102 * object. 103 */ 104 foreach($tableInfo['metadata'] as $columnName => $columnMetadata) { 105 $methodName = 'get'; 106 $columnNameParts = explode('_', $columnName); 107 108 foreach($columnNameParts as $columnNamePart) { 109 $methodName .= ucfirst($columnNamePart); 110 111 if($columnName == $pk) { 112 $getPkFunction .= ucfirst($columnNamePart); 113 $setPkFunction .= ucfirst($columnNamePart); 114 } 115 } 116 117 if(!method_exists($model, $methodName)) { 118 $className = get_class($model); 119 Core_Log::log("Function \"$methodName\" does not exist in \"$className\". Either implement the function or override the save() function.", Core_Log::ERR); 120 throw new Exception("Function \"$methodName\" does not exist in \"$className\". Either implement the function or override the save() function."); 121 } 122 123 $data[$columnName] = $model->$methodName(); 124 } 125 126 if(null === ($id = $model->$getPkFunction())){ 127 unset($data[$pk]); 128 Core_Log::log($className . " returned null as primary key and could not be deleted", Core_Log::ERR); 129 } 130 else{ 131 unset($data[$pk]); 132 $where = $this->getDbTable()->getAdapter()->quoteInto("$pk = ?", $model->$getPkFunction()); 133 $this->getDbTable()->delete($where); 134 } 135 unset($model); 136 } 137 138 public function find($id, Core_Model_Abstract $model = null){ 139 $result = $this->getDbTable()->find($id); 140 if(0 == count($result)){ 141 return; 142 } 143 $row = $result->current(); 144 if($model === null) { 145 $model = $this->_createModel($row); 146 return $model; 147 } 148 $this->_populate($model, $row); 149 } 150 151 public function fetchAll() { 152 return $this->_fetchAll(); 153 } 154 155 protected function _fetchAll($selectStatement = null){ 156 $result = $this->getDbTable()->fetchAll($selectStatement); 157 158 if(0 == count($result)){ 159 return null; 160 } 161 162 $models = array(); 163 foreach($result as $row){ 164 $model = $this->_createModel($row); 165 array_push($models, $model); 166 } 167 168 return $models; 169 } 170 171 protected function _fetchRow($selectStatement) { 172 $result = $this->getDbTable()->fetchRow($selectStatement); 173 174 if(0 == count($result)){ 175 return; 176 } 177 178 $model = $this->_createModel($result); 179 180 return $model; 181 } 182 183 /** 184 * 185 * Creates the model and if supplied sets the options 186 * @param array $options 187 * @throws Exception 188 */ 189 protected function _createModel($options = null) { 190 if(is_string($this->_modelClass) && class_exists($this->_modelClass)){ 191 if($options) { 192 $options = $options->toArray(); 193 } 194 $model = new $this->_modelClass($options); 195 $model->setIsSaved(true); 196 return $model; 197 } 198 else { 199 throw new Exception('Invalid model class provided ' . $this->_modelClass); 200 } 201 } 202 203 protected function _beginTransaction() { 204 $this->getDbTable()->getDefaultAdapter()->beginTransaction(); 205 } 206 207 protected function _commit() { 208 $this->getDbTable()->getDefaultAdapter()->commit(); 209 } 210 211 protected function _rollBack() { 212 $this->getDbTable()->getDefaultAdapter()->rollBack(); 213 } 214 }

Som synas så jobbar vår Gateway emot Core_Db_Table_Abstract objekt. Dessa är egentligen bara en specialisering av Zend_Db_Table_Abstract. Det hade även gått att ha SQL kod direkt i vår Gateway om man så önskar. Core_Db_Table_Abstract ser ut som följer.

1 <?php 2 abstract class Core_Db_Table_Abstract extends Zend_Db_Table_Abstract 3 { 4 static function getPrimaryKey() { 5 if(!empty($this)) { 6 if(count($this->_primary) > 0) { 7 $pk = $this->_primary; 8 } else { 9 $tableInfo = $this->info(); 10 $pk = $tableInfo['primary']; 11 } 12 } 13 else { 14 $calledClassName = get_called_class(); 15 $table = new $calledClassName(); 16 if(count($table->_primary) > 0) { 17 $pk = $table->_primary; 18 } else { 19 $tableInfo = $table->info(); 20 $pk = $tableInfo['primary']; 21 } 22 unset($table); 23 } 24 return $pk; 25 } 26 27 static function getName() { 28 if(!empty($this)) { 29 $name = $this->_name; 30 } 31 else { 32 $calledClassName = get_called_class(); 33 $table = new $calledClassName(); 34 $name = $table->_name; 35 unset($table); 36 } 37 return $name; 38 } 39 } 40 41

Vad allt detta resulterar i är att du nu kommer jobba med dina modeller utan att egentligen behöva tänka på den bakomliggande lagringen.

1 // Skapa en ny user och sätt data 2 $user = new Core_Model_User(); 3 $user->setName('Foo Bar'); 4 5 // Låt vår gateway, som kan jobba mot DB 6 // eller någon annan lagring spara den åt oss. 7 // Vår gateway sköter också om detta är en 8 // 'insert' eller 'update' 9 Core_Model_UsersGateway::getInstance()->save($user);

Vår Core_Model_UsersGateway använder vi för att spara och hämta Users enligt olika kriterier. Exempelvis

1 <?php 2 class Core_Model_UsersGateway extends Core_Model_Gateway_Db 3 { 4 protected $_dbTableClass = 'Core_Model_DbTable_Users'; 5 protected $_modelClass = 'Core_Model_User'; 6 7 public function fetchCurrent() { 8 if(!Zend_Auth::getInstance()->hasIdentity()) { 9 return null; 10 } 11 12 $userInfo = Zend_Auth::getInstance()->getStorage()->read(); 13 $currentUser = $this->fetchWhereId($userInfo->user_id); 14 15 return $currentUser; 16 } 17 18 public function fetchWhereId($userId) { 19 return $this->_fetchRow($this->getDbTable()->select() 20 ->where('user_id = ?', $userId)); 21 } 22 }

Jag har valt att döpa funktionerna med namn hämtade inspirerade ifrån queryn de kör. Men det är en smaksak.

Funktioner i User kan självklart innehålla all möjlig logik och inte bara sätta och returnera data. Säg att vi hade haft en funktion som hade varit getUnreadMessages() på vår User. Det hade också löst sig utan att bry sig om var/hur våra Messages var lagrade då även dessa använder sig av en Gateway. I User hade vi då tex kunna haft något i stil med.

1 class User extends Core_Model_Abstract 2 { 3 ... 4 5 public function getUnreadMessages() { 6 return Messages_Model_Gateway::getInstance()->fetchAllUnreadForUserId($this->_userId); 7 } 8 9 ... 10 } 11 12 13 // Nu kan du hämta ut den aktiva användaren, 14 // ändra dennes namn och spara ner ändringen. 15 // Allt utan att behöva bry dig alls om vad som 16 // händer i bakgrunden. 17 $currentUser = Core_Model_UsersGateway::getInstance()->fetchCurrent(); 18 $currentUser->setName('Bar Foo'); 19 Core_Model_UsersGateway::getInstance()->save($currentUser); 20 21 // Och så printar vi ut alla headers ifrån olästa 22 // meddelanden också. 23 $unreadMessages = $currentUser->getUnreadMessages(); 24 25 foreach($unreadMessages as $unreadMessage) { 26 echo $unreadMessage->getHeader(); 27 }

Det är naturligtvis lite jobb att få detta på plats och jag optimerar och finslipar detta kontinuerligt. Men fördelarna tycker jag är betydande. Jag jobbar mot Zend Framework, därav formaten på namnen på klasser etc, men det går lika bra att implementera detta i “ren” PHP eller mot något annat ramverk.

flattr this!

Synen på framtiden

29 april, 2011 av Daniel Liljeberg Inga kommentarer »

CFO asks his CEO, ”What happens if we invest in developing our people and then they leave the company?”
CEO answers, ‘What happens if we don’t, and they stay?”

flattr this!

Lön för mödan

11 april, 2011 av Daniel Liljeberg Inga kommentarer »

Svarade häromdagen återigen på en del frågor om olika utvecklingsverktyg, språk och metodiker. När det hela, som tog ca 30 min, var färdigt så sa intervjuaren.

-“Som tack för att du tog dig tid och svara på dessa frågor så erbjuder vi en symbolisk summa på 500 kr i form av ett presentkort”

Man kunde välja mellan en del olika presentkort eller att skänka pengarna. Efter en lång hård vecka med sjuka barn så tyckte jag dock att det smått egoistiska valet av 500 kr på Systembolaget lät som ett trevligt alternativ. Men det gnagde lite inom mig efteråt och jag bestämde mig att väga upp genom att personligen donera samma summa Ler.

Men, det som iaf slog mig efter denna händelsen var, “Kan inte alla telefonförsäljare, marknadsundersökare och intervjuare ta efter detta?”.

Man skulle ju bli rik Blinkar

flattr this!

Keep an open mind

31 mars, 2011 av Daniel Liljeberg Inga kommentarer »

Idag fick jag en förfrågan av ett par killar på högskolan om att svara på en enkät gällande olika programmeringsspråk, vilka jag använder, hur jag använder dem och även betygsätta språken i olika kategorier så som säkerhet, prestanda etc.

Något som snabbt slog mig var de gamla minnena ifrån studietiden då vi var två läger, C++ och Java. Det ena hade bara ont att säga om det andra. Java supportrarna stoltserade med att “allt fanns färdigt i bibliotek” och C++ anhängarna förklarade att “C++ låter dig göra vad du vill, men kräver att du vet vad du pysslar med”, något som lite underförstått betydde att Java-nissarna inte hade lika bra koll. Det hela var egentligen mest oskyldigt smutskastande mellan två olika inriktningar på en utbildning, men samma läger kunde sedan, och kan till viss del även idag, återfinnas bland anhängare av olika språk eller plattformar.

Jag har dock under mina många år i branschen lärt mig att ha ett öppet sinne. Alla språk som existerar och kontinuerligt används idag gör det för att de funnit en nisch som de passar för. Det betyder inte att alla språk är underbara att jobba i men man bör försöka att inte förblindas av namnet på ett språk utan istället försöka se vad det är man försöker lösa. VAD skall vi göra, och vilket språk passar BÄST för den uppgiften.

C++ fungerade ypperligt till spelutveckling, vilket var det vi satt med under utbildningen. Men jag skulle nog behöva ha otroligt få val till hands innan jag skulle sätta mig ner och koda webutveckling i C++. Inte för att det inte går, för det gör det, utan för att det helt enkelt finns andra språk som lämpar sig bättre för ändamålet.

På samma sätt skulle jag förmodligen hellre välja C för kodning av inbyggda system istället för Visual Basic. Ibland uppstår gråzoner där två språk faktiskt kan fungera i princip lika bra, .Net och PHP för webbutveckling tex. Men istället för att hamna i det ena eller andra lägret och per automatik endast spy galla över det andra alternativet så har jag lärt mig att man får en mycket bättre förståelse om man är öppen för båda språken och försöker hitta deras styrkor och svagheter. Valet av utvecklingsspråk handlar ofta om företagets tidigare investeringar, framtida planer, medarbetares kompetenser, kunders önskemål och tekniska begränsning. Det är inte alltid alla dessa variabler kommer fram till att det är ditt favoritspråk som väljs. Kort och gott, keep an open mind Blinkar

Och till mina gamla Java-nissar från utbildningen, förlåt om ni blev ledsna i ögat…

flattr this!

Efter VAIL FAIL så blev det Amahi

24 mars, 2011 av Daniel Liljeberg 8 kommentarer »

Jag installerade för några månader sedan WHS Vail på min Scaleo. Detta eftersom jag fått en system crash, pga strömavbrott, av min WHS V1 och factory restore inte ville fungera och en ominstallation ifrån grunden ändå var att vänta i och med detta. Dock tog det bara några dagar innan Microsoft släppte ytterliggare en update, men i och med denna kom nyheten om att de övergav Drive Extender (DE). DE är den pooling teknologi som gjort det så lätt att lägga till diskar till WHS och en selling point för många.

Det har debatterats friskt kring detta och prestanda problem, tillsammans med en konsolidering mellan Small Business Server och WHS teamen, har pekats ut som en anledning till att MS ville gå ifrån DE. Man har på senare tid lagt till lite funktionalitet igen rörande att filer läggs på mer än en disk osv, men inget som är lika enkelt som DE när det handlar om att lägga till nya diskar osv. Vill man följa lite av diskussionerna och reaktionerna kring ämnet så finns dessa på

http://windowsteamblog.com/windows/b/windowshomeserver/

För mig så betydde detta att jag stod inför en ominstallation till. Men då bestämde jag mig för att göra något jag länge funderat på. Jag installerade Ubuntu Server på den och installerade allt jag behövde för att få den funktionalitet jag önskade mig. Jag skissade lite på en egen DE funktionalitet för Linux, men i och med detta fick jag höra talas om Greyhole. Den arbetade i precis så som min ursprungliga idé såg ut så jag bestämde mig för att testa. Då läste jag att Grehole installerades som standard med Amahi, “men vad är Amahi” tänkte jag då.

Amahi visade sig vara en i princip komplett Home Server baserad på Fedora 10. Nu heter den Amahi6 och är uppdaterad och körs på Fedora 14. Med Greyhole så får du i princip det du hade med DE och även om en del saker fortfarande måste konfigureras via terminalen så görs det mesta ifrån ett webgränssnitt. Det finns en “AppStore” där färdiga paket av testade program finns för 1-click-installationer. Det kan tex handla om att installera Media-Wiki, uTorrent, Transmission, SpeedTest Mini, DLNA, WordPress, SABnzbd, CouchPotato, Sick Beard,  Squeezebox Server, MythTV Backend, CrashPlan osv, osv, osv.

Nya “Appar” kommer hela tiden och skaran som använder Amahi har nyligen växt en hel del med folk som just övergett WHS. Jag kan inte säga att Amahi är rätt för alla, men jag komemr nog inte gå tillbaka till WHS på mycket länge. Jag har i princip all funktionalitet jag använde mig av i WHS, plus VPN som standard, plus stabiliteten i både filsystem och drift som Linux erbjuder.

Om ni som jag tröttnat lite på MS hantering av WHS så rekommenderar jag er att läsa om Amahi på länken nedan. Frågor om mina erfarenheter med Amahi på våra kära Scaleo’s svarar jag gärna på

https://www.amahi.org

Lite bilder av min Amahi

image

image

image

image

image

flattr this!

WordPress SEO fine-tune by Meta SEO Pack from Poradnik Webmastera