Ăven om Yii DAO kan hantera sĂ„ gott som varje databasrelaterad uppgift, Ă€r det stor risk att vi anvĂ€nder 90% av vĂ„r tid till att skriva vissa SQL-satser som genomför de Ă„terkommande CRUD-operationerna (create, read, update och delete). Det Ă€r ocksĂ„ svĂ„rt att underhĂ„lla koden nĂ€r den Ă€r uppblandad med SQL-satser. En lösning pĂ„ detta problem Ă€r att anvĂ€nda Active Record.
Active Record (AR) Àr en populÀr teknik för objekt-relationsmappning (ORM).
Varje AR-klass representerar en databastabell (eller -vy) vars attribut Àr
representerade som AR-klassens propertyn, en AR-instans representerar en rad i
nÀmnda tabell. Vanliga CRUD-operationer Àr implementerade som AR-metoder.
Resultatet Àr tillgÄng till data pÄ ett mer objektorienterat sÀtt. Till exempel,
kan följande kod anvÀndas för att sÀtta in en ny rad i tabellen Post:
$post=new Post;
$post->title='sample post';
$post->content='post body content';
$post->save();I det följande beskrivs hur man sÀtter upp AR och anvÀnder denna till att
genomföra CRUD-operationer. I nÀsta avsnitt visas hur man kan anvÀnda AR för att
hantera databassamband (relationship). För enkelhets skull kommer nedanstÄende
databastabell att anvÀndas i exemplen i detta avsnitt. MÀrk att om MySQL-databas
anvÀnds skall AUTOINCREMENT bytas mot AUTO_INCREMENT i följande SQL.
CREATE TABLE Post ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(128) NOT NULL, content TEXT NOT NULL, createTime INTEGER NOT NULL );
MÀrk: AR Àr inte tÀnkt att lösa alla databasrelaterade uppgifter. Tekniken kommer bÀst till anvÀndning för att modellera databastabeller i form av PHP-konstruktioner samt genomföra sÄdana frÄgor som inte inbegriper komplexa SQL-satser. Yii:s DAO bör anvÀndas för mer komplexa scenarier.
AR förlitar sig pÄ en databasanslutning för att genomföra databasrelaterade
operationer. Som standard antar den att applikationskomponenten db ger den
CDbConnection-instans som behövs till att tjÀna som databasanslutning.
Följande applikationskonfiguration visar ett exempel:
return array(
'components'=>array(
'db'=>array(
'class'=>'system.db.CDbConnection',
'connectionString'=>'sqlite:path/to/dbfile',
// turn on schema caching to improve performance
// 'schemaCachingDuration'=>3600,
),
),
);Tips: Eftersom Active Record förlitar sig pÄ metadata om tabeller för att avgöra information om kolumner, ÄtgÄr tid till att lÀsa metadata och till att analysera den. Om det Àr mindre troligt att databasschemat kommer att Àndras, bör schemacachning slÄs pÄ genom att konfigurera CDbConnection::schemaCachingDuration-propertyn till ett vÀrde större Àn 0.
Stödet för AR beror av anvÀnd databashanterare. För nÀrvarande stöds endast följande databashanterare:
MÀrk: Stöd för Microsoft SQL Server har varit tillgÀngligt sedan version 1.0.4; Stöd för Oracle har varit tillgÀngligt sedan version 1.0.5.
Vid önskemÄl om att anvÀnda en annan applikationskomponent Àn db, eller om att
anvÀnda AR till att arbeta mot flera databaser, ÄsidosÀtt
CActiveRecord::getDbConnection(). Klassen CActiveRecord utgör basklass för alla
AR-klasser.
Tips: Det finns tvÄ sÀtt att arbeta mot multipla databaser i AR. Om databasernas scheman Àr olika, kan man skapa olika AR-basklasser med skild implementering av getDbConnection(). I annat fall Àr det en bÀttre idé att dynamiskt Àndra den statiska variabeln CActiveRecord::db.
För tillgÄng till en databastabell mÄste först en AR-klass definieras genom arv
och utvidgning av CActiveRecord. Varje AR-klass representerar en enda
databastabell och en AR-instans representerar en rad i den tabellen. Följande
exempel visar den minimala kod som erfordras för AR-klassen korresponderande
mot tabellen Post.
class Post extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}Tips: Eftersom AR-klasser ofta refereras till pÄ mÄnga stÀllen, Àr det möjligt att importera hela katalogen som innehÄller AR-klasserna, i stÀllet för att inkludera dem en och en. Till exempel, om alla AR-klasser finns i katalogen
protected/models, kan applikationen konfigureras som följer:return array( 'import'=>array( 'application.models.*', ), );
Som standard Àr namnet pÄ AR-klassen samma som namnet pÄ databastabellen. à sidosÀtt metoden tableName() om de skall vara olika. Metoden model() finns deklarerad per se i varje AR-klass (förklaring följer lÀngre ned).
KolumnvÀrden för en rad i en tabell kan Ätkommas som propertyn i den
motsvarande AR-klassinstansen. Till exempel, följande kod sÀtter kolumnen (attributet) title:
$post=new Post;
$post->title='a sample post';Ăvensom vi aldrig uttryckligen deklarerar title-propertyn i klassen Post,
kan vi fortfarande fÄ tillgÄng till den i ovanstÄende kod. Detta beror pÄ att
title Àr en kolumn i tabellen Post och CActiveRecord gör den tillgÀnglig som
en property med hjÀlp av PHP:s "magiska" metod __get(). En exception
signaleras vid försök att tillgÄ en icke-existerande kolumn pÄ detta sÀtt.
Info: I denna guide namnges kolumner med kamelnotation (t.ex.
createTime). Detta beror pÄ att kolumner adresseras som vanliga objektpropertyn, vilka ocksÄ de namnges med kamelnotation. Men Àven om kamelnotation fÄr PHP-koden att se mer konsekvent ut i frÄga om namngivning, kan detta introducera problem med skiftlÀgeskÀnslighet (case-sensitivity) för vissa DBMS. Till exempel PostgreSQL betraktar som standard kolumnnamn som oberoende av skiftlÀge, och om ett kolumnnamn i en frÄga innehÄller bÄde gemena och versaler mÄste det omges av citationstecken. Av denna anledning kan det vara klokt att namnge kolumner (och Àven tabeller) enbart med gemena (t.ex.create_time), för att undvika ev. skiftlÀgesrelaterade problem.
För att sÀtta in en ny rad i en databastabell, skapa en ny instans av den motsvarande AR-klassen, sÀtt dess propertyn associerade med tabellens kolumner och anropa metoden save() för att genomföra insÀttningen.
$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->createTime=time();
$post->save();Om tabellens primÀrnyckel Àr sjÀlvupprÀknande, kommer AR-instansen att efter
insÀttningen innehÄlla en uppdaterad primÀrnyckel. I ovanstÄende exempel
Äterspeglar id-propertyn primÀrnyckelns vÀrde i den nyligen insatta
postningen, trots att vi aldrig uttryckligen Àndrar den.
Om en kolumn Àr definierad med nÄgot statiskt standardvÀrde (t.ex. en strÀng, ett tal) i tabellschemat, kommer motsvarande property i AR-instansen att automatiskt innehÄlla ett sÄdant vÀrde nÀr instansen skapats. Ett sÀtt att Àndra ett sÄdant standardvÀrde Àr genom att uttryckligen deklarera propertyn i AR-klassen:
class Post extends CActiveRecord
{
public $title='please enter a title';
......
}
$post=new Post;
echo $post->title; // this would display: please enter a titleMed start i version 1.0.2, kan ett attribut tilldelas ett vÀrde av typen
CDbExpression innan posten sparas till databasen (antingen insÀttning eller
uppdatering). Exempelvis, för att spara en tidstÀmpel, returnerad av MySQL:s
funktion NOW(), kan följande kod anvÀndas:
$post=new Post;
$post->createTime=new CDbExpression('NOW()');
// $post->createTime='NOW()'; will not work because
// 'NOW()' will be treated as a string
$post->save();Tips: Ăven om AR tillĂ„ter oss att utföra databasoperationer utan att skriva arbetskrĂ€vande SQL-satser, vill vi ofta veta vilka SQL-satser som exekveras av AR. Detta kan uppnĂ„s genom att slĂ„ pĂ„ Yii:s loggningsfiness. Till exempel kan vi aktivera CWebLogRoute i applikationskonfigurationen, vilket leder till att exekverade SQL-satser kan avlĂ€sas i slutet av varje webbsida. Sedan version 1.0.5, kan vi sĂ€tta CDbConnection::enableParamLogging till true i applikationskonfigurationen sĂ„ att Ă€ven parametervĂ€rden knutna till SQL-satserna loggas.
För att lÀsa data i en databastabell, anropa nÄgon av följande find-metoder:
// leta upp den första raden som satisfierar angivet villkor
$post=Post::model()->find($condition,$params);
// leta upp raden med angiven primÀrnyckel
$post=Post::model()->findByPk($postID,$condition,$params);
// leta upp en rad som har angivna attributvÀrden
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// leta upp den första raden genom anvÀndning av specifierad SQL-sats
$post=Post::model()->findBySql($sql,$params);I ovanstÄende, anropas metoden find medelst Post::model(). Som tidigare
nÀmnts Àr den statiska metoden model() obligatorisk i varje AR-klass. Metoden
returnerar en AR-instans som anvÀnds för att fÄ tillgÄng till metoder pÄ
klassnivÄ (nÄgot liknande statiska klassmetoder) i en objektkontext.
Om find-metoden hittar en rad som satisfierar frÄgevillkoren, kommer den att
returnera en instans av Post vars propertyn innehÄller korresponderande
kolumnvÀrde frÄn tabellraden. De laddade vÀrdena kan sedan lÀsas pÄ samma sÀtt
som vanliga objektpropertyn, till exempel, echo $post->title;.
Metoden find returnerar null om inget kan hittas i databasen med det givna
frÄgevillkoret.
I anropet till find anvÀnds $condition och $params för att specificera
frÄgevillkor. HÀr kan $condition vara en strÀng som representerar WHERE-
ledet i en SQL-sats, $params en array av parametrar vars vÀrden kommer att
kopplas till platshÄllaren i $condition. Till exempel,
// find the row with postID=10
$post=Post::model()->find('postID=:postID', array(':postID'=>10));MÀrk: I ovanstÄende exempel kan, för vissa databashanterare, referensen till kolumen
postIDbehöva omges av escapetecken. Till exempel, om PostgreSQL anvÀnds, behöver vi skriva villkoret som"postID"=:postIDeftersom PostgreSQL som standard betraktar kolumnnamn som skiftlÀgesokÀnsliga.
$condition kan ocksÄ anvÀndas för att specificera mer komplexa frÄgevillkor. I
stÀllet för en strÀng kan $condition vara en instans av CDbCriteria, vilken
tillÄter oss att specificera andra villkor Àn enbart WHERE-ledet. Till exempel,
$criteria=new CDbCriteria;
$criteria->select='title'; // only select the 'title' column
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params is not neededMÀrk vÀl att nÀr CDbCriteria anvÀnds som frÄgevillkor, behövs inte $params-
parametern eftersom den kan specificeras i CDbCriteria, vilket exemplifieras
ovan.
Ett alternativt sÀtt att anvÀnda CDbCriteria Àr genom att lÀmna med en array
till find-metoden. Arrayens nycklar och vÀrden motsvarar kriterieobjektets
respektive propertynamn och -vÀrden. OvanstÄende exempel kan skrivas om som
följer,
$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));Info: NÀr ett frÄgevillkor handlar om att matcha nÄgra kolumner mot specificerade vÀrden, kan findByAttributes() anvÀndas. Vi lÄter dÄ
$attributes-parametern vara en array med vĂ€rden indexerade av kolumnnamnen. I vissa ramverk kan denna uppgift fullgöras genom anrop till metoder i stil medfindByNameAndTitle. Ăven om detta tillvĂ€gagĂ„ngssĂ€tt förefaller attraktivt, leder det ofta till förvirring, konflikter samt problem som kĂ€nslighet för kolumnnamns skiftlĂ€ge (case).
Om mer Àn en rad med data matchar det specificerade frÄgevillkoret, kan samtliga
hÀmtas in tillsammans med hjÀlp av följande findAll-metoder, vilka var och en
har en motsvarande find-metod, sÄ som beskrivits ovan.
// leta upp alla rader som satisfierar angivet villkor
$posts=Post::model()->findAll($condition,$params);
// leta upp alla rader med den specificerade primÀrnyckeln
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// leta upp alla rader som har angivna attributvÀrden
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// leta upp alla rader genom anvÀndning av specifierad SQL-sats
$posts=Post::model()->findAllBySql($sql,$params);Om inget matchar frÄgevillkoret, returnerar findAll en tom array. Detta
skiljer sig frÄn find som skulle returnera null om inget hittades.
Förutom find- och findAll-metoderna som beskrivs ovan, finns Àven följande
metoder tillgÀngliga:
// berÀkna antalet rader som satisfierar angivet villkor
$n=Post::model()->count($condition,$params);
// berÀkna antalet rader genom anvÀndning av specifierad SQL-sats
$n=Post::model()->countBySql($sql,$params);
// undersök om det finns Ätminstone en rad som satisfierar angivet villkor
$exists=Post::model()->exists($condition,$params);Efter det att en AR-instans initialiserats med kolumnvÀrden, kan dessa Àndras och sparas tillbaka till databastabellen.
$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // save the change to databaseSom synes, anvÀnds samma save()-metod för bÄde
insÀttnings- och uppdateringoperationer. Om en AR-instans skapas med hjÀlp av
new-operatorn, leder anrop av save() till insÀttning av
en ny post i databastabellen; om en AR-instans Àr resultatet frÄn nÄgon anrop av
metoderna find eller findAll, leder anrop av save()
till uppdatering av den existerande raden i tabellen. Faktum Àr att
CActiveRecord::isNewRecord kan anvÀndas för att upplysa om huruvida en AR-
instans Àr ny eller inte.
Det Àr ocksÄ möjligt att uppdatera en eller flera rader i en databastabell utan att först ladda dem. AR tillhandahÄller följande ÀndamÄlsenliga metoder pÄ klassnivÄ för ÀndamÄlet:
// uppdatera raderna som matchar det specificerade villkoret
Post::model()->updateAll($attributes,$condition,$params);
// uppdatera raderna som matchar det specificerade villkoret och primÀrnyckel/-nycklar
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// uppdatera rÀknarkolumnerna i raderna som satisfierar det specificerade villkoret
Post::model()->updateCounters($counters,$condition,$params);I ovanstÄende, Àr $attributes en array med kolumnvÀrden indexerade av
kolumnnamn; $counters Àr en array med inkrementella vÀrden indexerade av
kolumnnamn samt $condition och $params Àr som beskrivits i föregÄende
delavsnitt.
Det gÄr ocksÄ att ta bort en rad med data om en AR-instans har initialiserats med denna rad.
$post=Post::model()->findByPk(10); // assuming there is a post whose ID is 10
$post->delete(); // delete the row from the database tableLÀgg mÀrke till att efter borttagningen förblir AR-instansen oförÀndrad nÀr den motsvarande raden i databastabellen redan Àr borttagen.
Följande metoder pÄ klassnivÄ finns tillgÀngliga för att ta bort rader utan att först behöva ladda dem:
// tag bort raderna som matchar det angivna villkoret
Post::model()->deleteAll($condition,$params);
// tag bort raderna som matchar det angivna villkoret och primÀrnyckel/-nycklar
Post::model()->deleteByPk($pk,$condition,$params);Vid insÀttning eller uppdatering av en rad behöver vi ofta kontrollera ifall kolumnvÀrden Àr i överensstÀmmelse med vissa regler. Detta Àr av speciell vikt om kolumnvÀrdena tillhandahÄlls frÄn slutanvÀndare. Generellt sett skall vi inte lita blint pÄ nÄgot som kommer frÄn klientsidan.
AR utför datavalidering automatiskt nÀr save() anropas. Valideringen baseras pÄ de regler som fins specificerade i metoden rules() i AR-klassen. Fler detaljer angÄende specificering av valideringsregler Äterfinns i sektionen Deklarera valideringsregler. Nedan ses det typiska arbetsflödet som behövs för att spara en databaspost:
if($post->save())
{
// data is valid and is successfully inserted/updated
}
else
{
// data is invalid. call getErrors() to retrieve error messages
}NÀr data för insÀttning eller uppdatering skickas av en slutanvÀndare i ett html-formulÀr, behöver vi tilldela dem till motsvarande AR-propertyn. Detta kan göras enligt följande :
$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();Om det handlar om mÄnga kolumner, kommer listan med tilldelningar att bli lÄng. Detta kan mildras genom anvÀndning av propertyn attributes sÄ som visas nedan. För fler detaljer se avsnittet SÀkra upp attributilldelningar samt avsnittet Skapa Action.
// assume $_POST['Post'] is an array of column values indexed by column names
$post->attributes=$_POST['Post'];
$post->save();Liksom tabellrader identifieras AR-instanser unikt genom sina primÀrnycklars vÀrden. Att jÀmföra tvÄ AR-instanser handlar dÀrför bara om att jÀmföra vÀrdena för deras primÀrnycklar, givet att de tillhör samma AR-klass. Ett enklare sÀtt Àr dock att anropa CActiveRecord::equals().
Info: Till skillnad mot AR-implementeringar i andra ramverk, stöder Yii sammansatta primÀrnycklar i sitt AR. En sammansatt primÀrnyckel bestÄr av tvÄ eller flera kolumner. Följaktligen Àr primÀrnyckelvÀrden i Yii representerade av en array. Propertyn primaryKey ger primÀrnyckelvÀrdet för en AR-instans.
CActiveRecord erbjuder nÄgra platshÄllarmetoder vilka kan ÄsidosÀttas i Àrvda klasser för att anpassa arbetsflödet i dessa.
beforeValidate och afterValidate: körs innan resp. efter att validering utförs.
beforeSave och afterSave: körs innan resp. efter en AR-instans sparas.
beforeDelete and afterDelete: körs innan resp. efter en AR- instans tas bort.
afterConstruct: körs varje gÄng en AR-
instans skapas med hjÀlp av operatorn new.
beforeFind: körs innan nÄgon av AR:s
'find'-metoder anvÀnds för att exekvera en frÄga (t.ex. find(), findAll()).
Detta har varit tillgÀngligt sedan version 1.0.9.
afterFind: körs varje gÄng en AR-instans har skapats som resultat av en frÄga.
Varje AR-instans innehÄller en property benÀmnd dbConnection, vilken Àr en instans av CDbConnection. SÄlunda kan transaction-finessen som tillhandahÄlls av Yii DAO anvÀndas med AR om sÄ önskas:
$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
// find and save are two steps which may be intervened by another request
// we therefore use a transaction to ensure consistency and integrity
$post=$model->findByPk(10);
$post->title='new post title';
$post->save();
$transaction->commit();
}
catch(Exception $e)
{
$transaction->rollBack();
}MÀrk: Stöd för namngivna omfÄng (named scopes) har varit tillgÀngligt sedan version 1.0.5. Ursprungsidén Àr hÀmtad frÄn Ruby on Rails.
Ett namngivet omfÄng representerar ett namngivet frÄgekriterium som kan kombineras med andra namngivna omfÄng och appliceras pÄ en Active Record-frÄga.
Namngivna omfÄng deklareras huvudsakligen i metoden CActiveRecord::scopes(),
i form av par av namn-kriterium. Följande kod deklarerar tvÄ namngivna omfÄng,
published och recently, i modellklassen Post:
class Post extends CActiveRecord
{
......
public function scopes()
{
return array(
'published'=>array(
'condition'=>'status=1',
),
'recently'=>array(
'order'=>'createTime DESC',
'limit'=>5,
),
);
}
}Varje namngivet omfÄng deklareras som en array vilken kan anvÀndas för att
initialisera en instans av CDbCriteria. Till exempel, det namngivna omfÄnget
recently specificerar att propertyn order till att vara createTime DESC
och propertyn limit till 5, vilket leder till ett frÄgekriterium som skulle
returnera de fem senast publicerade postningarna.
Namngivna omfÄng anvÀnds huvudsakligen som modifierare till metodanrop i find-familjen.
Multipla namngivna omfÄng kan lÀnkas till varandra och resultera i en mer restriktiv
resultatmÀngd frÄn en frÄga. Till exempel, för att hitta de senast publicerade postningarna
kan följande kod anvÀndas:
$posts=Post::model()->published()->recently()->findAll();Generellt mÄste namngivna omfÄng placeras till vÀnster om ett anrop till en find-metod.
Vart och ett av dem tillhandahÄller ett frÄgekriterium som kombineras med andra kriterier,
inklusive det som lÀmnas med i anropet find-metoden. Nettoeffekten blir som att lÀgga till
en lista av filter till en frÄga.
Med start fr o m version 1.0.6 kan namngivna omfÄng Àven anvÀndas med metoderna update och
delete. Till exempel följande kod skulle ta bort alla nyligen publicerade postningar:
Post::model()->published()->recently()->delete();MÀrk: Namngivna omfÄng kan endast anvÀndas för metoder pÄ klassnivÄ. Det innebÀr att metoden mÄste anropas pÄ formatet
Klassnamn::modell().
Namngivna omfÄng kan parametriseras. Ett exempel kan vara att anpassa antalet
postningar som adresseras av det namngivna omfÄnget recently. För att Ästadkomma
det behöver vi - istÀllet för att deklarera det namngivna omfÄnget i metoden
CActiveRecord::scopes - definiera en ny metod med lika namn som det namngivna omfÄnget:
public function recently($limit=5)
{
$this->getDbCriteria()->mergeWith(array(
'order'=>'createTime DESC',
'limit'=>$limit,
));
return $this;
}DÀrefter kan följande sats anvÀndas för att hÀmta de tre senast publicerade postningarna:
$posts=Post::model()->published()->recently(3)->findAll();Om parametern 3 ovan utelÀmnas kommer de fem senaste postningarna att hÀmtas, enligt förbestÀmt standardvÀrde.
En modellklass kan ha ett förbestÀmt namngivet omfÄng som kommer att ÄsÀttas
alla frÄgor (inklusive relationella sÄdana) avseende modellen. Till exempel kan
en webbplats som stöder flera sprÄk vilja begrÀnsa innehÄll som visas till
det sprÄk den aktuella anvÀndaren valt. Eftersom det kan bli mÄnga frÄgor
gÀllande webbplatsens innehÄll, kan ett förbestÀmt namngivet omfÄng lindra
detta problem. För att göra detta, ÄsidosÀtt metoden CActiveRecord::defaultScope enligt följande,
class Content extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"language='".Yii::app()->language."'",
);
}
}Nu kommer följande metodanrop att automatiskt anvÀnda frÄgekriteriet som definierades ovan:
$contents=Content::model()->findAll();MÀrk att ett förbestÀmt namngivet omfÄng endast gÀller för SELECT-frÄgor.
Det ignoreras för INSERT-, UPDATE- och DELETE-frÄgor.
Signup or Login in order to comment.