Idé til caching i Smarty

December 9, 2008 · Posted in Udvikling · Comment 

BedsteVen.dk viser jeg de seneste aktiviteter på forsiden. Det samme gør jeg på aktivitetssiden. Hver aktivitet indeholder en del databaseopslag. Eftersom min database-klasse som nævnt før automatisk gi’r mig en getFoo() og setFoo() hvis tabellen indeholder feltet foo. Valgte jeg at implementere noget simpelt caching på følgende måde:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{* Jeg har tilføjet et 'cache' felt i min aktivitetstabel *}
{assign var="cache" value=$activity->getCache()}
 
{if $cache}
 
  {$cache}
 
{else}
 
  {capture name=act}
    {* Her hentes og vises den enkelte aktivitet *}
  {/capture}
 
  {* Jeg viser det fangede indhold *}
  {$smarty.capture.act}
 
  {* Måske lidt grimt, men templaten kan også gemme indhold.
     Derfor gemmer jeg det genererede indhold her *}
  {$activity->setCache($smarty.capture.act)}
 
{/if}

Lidt stored procedure

December 8, 2008 · Posted in Udvikling · 2 Comments 

Som inspiration til stored procedure i MySQL har jeg lavet en lille eksempel.

Stored procedure er en nem måde at “gemme” noget SQL på serveren. Lave en funktion om man vil. Mit eksempel er igen lidt tænkt, men burde illustrere hvad man kan bruge stored procedures til.

Først opretter jeg to meget simple tabeller. En til brugere og en til rettigheder.

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE priv (
  userid INT NOT NULL auto_increment,
  area CHAR(10) NOT NULL,
  PRIMARY KEY (userid,area)
) ENGINE=MyISAM;
 
CREATE TABLE users (
  userid INT NOT NULL auto_increment,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (userid)
) ENGINE=MyISAM;

Når jeg opretter en bruger vil jeg gerne automatisk gi’ den bruger adgang til områderne foo og bar. Jeg skal altså indsætte en række i users-tabellen og to rækker i priv-tabellen. Rækkerne i priv skal matche det userid man indsatte i users.

Jeg laver følgende stored procedure:

1
2
3
4
5
6
7
8
CREATE PROCEDURE add_user (IN i VARCHAR(255))
BEGIN
  DECLARE u INT;
  INSERT INTO users (name) VALUES (i);
  SELECT LAST_INSERT_ID() INTO u;
  INSERT INTO priv(userid, area) VALUES(u, 'foo');
  INSERT INTO priv(userid, area) VALUES(u, 'bar');
END;

Hvor jeg som argument ta’r en VARCHAR(255). I min stored procedure indsætter jeg brugeren i users-tabellen. Henter userid via LAST_INSERT_ID() og indsætter to rækker i priv-tabellen med områderne foo, bar sammen med userid.

Tilsvarende laver jeg en remove_user() der kan slette brugeren og hans privilegier

1
2
3
4
5
6
7
CREATE PROCEDURE remove_user(IN i VARCHAR(255))
BEGIN  
  DECLARE u INT;
  SELECT userid INTO u FROM users WHERE name = i;
  DELETE FROM priv WHERE userid = u;
  DELETE FROM users WHERE userid = u;
END;

For at indsætte nogle brugere kalder jeg følgende:

1
2
3
CALL add_user('Anna');
CALL add_user('Bent');
CALL add_user('Casper');

Og for at slette kalder jeg:

1
Call remove_user('Bent');

Bemærk at hvis man opretter stored procedures via phpMyAdmin så skal man skifte delimiter fra ; til fx //.

Videresende sin smartlog-blog

December 7, 2008 · Posted in Udvikling · Comment 

Hermed et lille tip til hvordan man viderestiller sin smartlog-blog til an anden. Man kan ikke gøre det på den “rigtige” måde, men jeg løste det ved at lave følgende i min template:

1
2
3
4
5
6
7
8
9
10
<{if $single}>
  <{assign var="s" value=$posts.0->getHeadline()}>
<{elseif $tag}>
  <{assign var="s" value=$tag->getTag()}>
<{elseif $page}>
  <{assign var="s" value=$page->getHeadline()}>
<{/if}>
 
<meta http-equiv="Refresh" content="0;
      url=http://mbn.dk/?s=<{$s|escape}>">

Det er meget simpelt, og det virker de fleste gange. Eftersom smartlog.dk ikke kender den nye adresse videresender jeg til søgesiden i min blog.

En måde at bruge VIEWs på

December 6, 2008 · Posted in Udvikling · Comment 

Jeg har lavet en simpel klasse der repræsenterer en MySQL-tabel. Den er meget simpel, og i constructor’en angiver man tabelnavn og primærnøgle, og så laver den bare en

1
SELECT * FROM tabelnavn WHERE id = $id;

Hvor tabelnavn og id er variable. Når man laver en instans af klassen udfra en user-tabel med id 5 laver man en

1
$user = new User(5);

Det oversætter min klasse så til:

1
SELECT * FROM user WHERE userid = 5;

Hvis jeg vel at mærke har kaldt min tabel user og primærnøglen userid i min klasse. Det der efterfølgende sker er, at klassen hælder alle felterne over i et array, og jeg kan efterfølgende hive feltet "name" ud som $user->getName(), eller gemme via $user->setName('Morten') der udfører en

1
UPDATE tabel SET name = 'Morten' WHERE userid = 5;

For at spare nogle databaseopslag kan man som andet argument selv sende felterne med. Fx.

1
2
$row = mysql_fetch_assoc($result);
$user = new User($row['userid'], $row);

På den måde behøver min klasse ikke lave noget opslag. Det var egentlig ikke klassen jeg ville snakke om. Skriv hvis I vil se noget kode eller høre mere :)

Det jeg ville nævne var en smart finte (synes jeg selv :)). Først laver jeg en tabel til mine brugere:

1
2
3
4
5
6
CREATE TABLE user (
  userid INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  newsletter TINYINT(1) UNSIGNED NOT NULL,
  PRIMARY KEY (userid)
);

Så fylder jeg lidt folk i:

1
2
3
4
5
6
INSERT INTO user (name, gender, newsletter) VALUES
('Alfred', 1),
('Bent', 0),
('Conni', 1),
('Dorthe', 1),
('Else', 0);

Nu laver jeg et VIEW til folk der abonnerer på nyhedsbrevet:

1
2
CREATE VIEW newsletter AS
  SELECT *  FROM user WHERE newsletter = 1;

Det resultat som min VIEW returnerer kan jeg umiddelbart smide direkte over i min MySQL-klasse som:

1
2
3
4
$result = mysql_query("SELECT * FROM newsletter");
while($row = mysql_fetch_assoc($result)) {
  $user = new User($row['userid'], $row);
}

Min klasse laver aldrig noget opslag selv, og da den ved hvilken tabel det stammer fra kan jeg også fint gemme. Det er måske nemmere at se det anvendelige hvis mit VIEW var mere kompliceret. Men forestil jer at jeg fx vil sende en e-mail til alle brugere at BedsteVen.dk der har oprettet en annonce, men ikke tilføjet noget billede, og deres annonce udløber om 14 dage, og der er mindre end 10 personer der har set den. Så begynder det at være meget rart at have det som et VIEW.

Om at sende nyhedsbreve

December 5, 2008 · Posted in Udvikling · 2 Comments 

Jeg synes det er problematisk at sende mange nyhedsbreve ud. Især hvis alle nyhedsbrevene skal være unikke.

Jeg står tit i en situation hvor jeg har et site som fx BedsteVen.dk der er kodet i PHP op mod en MySQL database. For at kunne bruge al den logik der i forvejen findes på sitet, vælger jeg typisk at udsende de enkelte e-mails gennem PHP ved at kalde et script via wget.

Det er dejlig nemt at kode, men det er utrolig svært hvis man via et script skal sende mange mails (mange er mere end 300 mails).

PHP har en max_execution_time der afgør levetiden for scriptet. Typisk er den på 90 sekunder, og hvis man er længere tid om at sende end det, dør scriptet.

Jeg lavede nogle test på BedsteVen.dk, og via PHP’s mail() funktion kunne jeg over en periode på 90 sekunder udsende ca. 300 mails. Det svarer til 3,33 mails i sekundet.

Min løsning blev to-trins.

Først valgte jeg at begrænse mængden af mails til hvad serveren sikkert kunne klare:

1
2
3
4
$count = 3 * ini_get(max_execution_time);
foreach(getUsers($count) as $user) {
  $user->sendNewsletter();
}

Ovenstående skulle gerne illustrere at jeg henter 3×90=270 brugere ud, og sender dem et nyhedsbrev. Det burde gøre at scriptet ikke dør undervejs.

Næste trin var at jeg i bunden af scriptet indsatte et link til scriptet igen, og kalder wget rekursivt så den blev ved med at hente siden — indtil der ikke længere er noget link. Ovenstående eksempel skal derfor udvides til at checke for at jeg ikke sender samme mail to gange.

For at få wget til at opføre sig som jeg vil, kalder jeg den på følgende måde:

1
  wget -q -erobots=off -w 5 -l 100 -r --delete-after -nd [URL]

-q gør at der ikke kommer noget output
-erobots=off gør at den ikke henter robots.txt
-w 5 gør at wget venter 5 sek. mellem hver download
-l 100 sætter en grænse for hvor mange niveauer wget må følge
-r er rekursiv
--delete-after og -nd gør at wget rydder op efter sig selv

Om det er den smarteste måde at gøre det på ved jeg ikke rigtig. Det er én måde :)

Lidt mere INSERT

December 2, 2008 · Posted in Udvikling · Comment 

INSERT har en lille ekstra feature som de færeste kender til. De fleste kender INSERT IGNORE eller REPLACE INTO som man kan bruge hvis man indsætter noget der overlapper en unik eller primær nøgle. Men der findes også en ON DUPLICATE KEY det gør det muligt at lave lidt mere avancerere ting. Jeg demonstrerer med et lille eksempel.

Jeg har en tabel til tags:

1
2
3
4
5
CREATE TABLE tags (
  tag VARCHAR(255),
  cnt INT UNSIGNED DEFAULT '1',
  PRIMARY KEY (tag)
) ENGINE = MYISAM;

Bemærk at cnt som standard er 1 og at tag er min primære nøgle. Jeg vil i tag gemme tag-navnet og i cnt gemme antallet af tags. Jeg indsætter data på følgende måde:

1
2
3
4
5
6
7
8
INSERT INTO tags (tag) VALUES ('foo')
   ON DUPLICATE KEY UPDATE cnt=cnt+1;
 
INSERT INTO tags (tag) VALUES ('foo')
   ON DUPLICATE KEY UPDATE cnt=cnt+1;
 
INSERT INTO tags (tag) VALUES ('foo')
   ON DUPLICATE KEY UPDATE cnt=cnt+1;

Feltet cnt indeholder nu 3, og der findes kun én række med “foo”.

Første gang findes foo ikke i forvejen, og cnt bliver derfor 1. Anden gang er foo der og cnt bliver talt op. Samme gør sig gældende de efterfølgende gange.

Det er måske et lidt tænkt eksempel, men det illustrerer rimelig godt hvordan ON DUPLICATE KEY virker.


  • Om websmed.dk

    Jeg hedder Morten, og jeg har udviklet webapplikationer siden slutningen af 90'erne.

    Jeg vil her dele ud af min erfaring, og med jævne mellemrum poste nogle tips og tricks, samt løsningsforslag på generelle dagligdags problemstillinger.

    Jeg har en forkærlighed for PHP og MySQL, hvorfor I nok vil se flest indlæg der vedrører den gren af webudvikling.

    Jeg har en anden blog af mere personlig karakter på mbn.dk, hvor I kan finde kontaktmuligheder m.m.

    Mine indlæg vil tit være baseret på problemstillinger i mit daglige virke, eller i mit eget firma MRLYTICS, hvor jeg sælger kundeundersøgelser samt kundedatabaser.

    Denne side er hostet hos slicehost.

  • Skrevet på Twitter