Whitepaper: Performance-optimering af Magento

Posted august 25th, 2009 in Udvikling by Anders Rasmussen

Varien har netop udgivet en 28-siders guide til optimering af Magentos ydelse, der inkluderer en række tips til serverkonfiguration, caching, skalering, benchmarking mv. Dokumentet er baseret på Enterprise Edition, men de fleste ting er lige så relevante for Community Edition.

Dette er et must-read for Magento-interesserede serveradministratorer, mens den typiske shop-ejer nok vil få lidt rigeligt tech talk galt i halsen.

IE6 dør i 2011

Posted juni 23rd, 2009 in Nyheder, Udvikling by Anders Rasmussen

Internet Explorer 6 har længe været en plage for alle webudviklere, der må spilde store mængder af tid på at tilpasse webdesigns til den uartige browser, der bare vil være anderledes end alle de andre. Selvom det er 8 år siden at det sorte får blev født, og både IE7 og IE8 i mellemtiden er kommet på markedet har den stadig en markedsandel på 13,5 % i Danmark og er den næstmest benyttede browser (april 2009).

Jeg har kigget lidt på markedsandelens udvikling gennem de sidste 2 år og tilpasset en eksponentiel tendenslinie for at se, hvornår vi kan slippe af med IE6. Denne (ikke særligt videnskabelige) undersøgelse viser, at halveringstiden for IE6 er på ca. 10 måneder (det samme som grundstof-isotopen mangan-54), og at det dermed vil tage 2 år før procentsatsen kommer ned på et ubetydeligt niveau.

IE6 markedsandel

Markedsandel for IE6. Kilde: fdim.dk

Øget fokus på Magento SEO

Posted marts 19th, 2009 in Nyheder, Udvikling by Anders Rasmussen

På Magentos blog er der netop blevet præsenteret et Magento SEO team, som skal hjælpe Magento-udviklerne med at forbedre Magentos søgemaskineoptimering (SEO). Foreløbig er der dog kun en enkelt person på holdet, hollænderen Joost de Valk, men jeg ser alligevel positivt på denne udmelding, da Google er netbutikkernes vigtigste kilde til kunder.

Nu er Magento jo ikke ligefrem dårlig i forhold til Google – tværtimod er Google-venlige URL’s, titler, meta-styring m.m. indbygget – men der er stadig meget, man kan gøre som shopudvikler eller -ejer for at forbedre indeksering og rangering i Google. Til dette formål har hollænderen udgivet en fremragende guide til Magento SEO, som bestemt er et kig værd for folk med lidt teknisk kendskab til Magento.

Idé: Lynhurtig shop med statisk caching

Posted januar 26th, 2009 in Udvikling by Anders Rasmussen

Magento er et ret stort og tungt system, hvilket kan ses på loading-tiderne, især hvis man benytter low end hostingløsninger. For at gøre loading-tiderne nogenlunde acceptable benytter Magento forskellige former for caching af blok-HTML, konfiguration, layout-XML mv. Min idé er en udvidelse af dette: Hvorfor ikke cache fuldstændigt statiske HTML-filer af hver enkelt side?

Indrømmet, jeg er ikke opfinder af statisk caching. Ideen har jeg fra Typo3, som har en extension, der gør netop dette. Det seneste eksempel jeg har set, er en netop lanceret shop, bygget i Typo3, med statisk caching: Stoleshoppen. Prøv at klikke lidt rundt på den. Farten kan man i hvert fald ikke klage over.

Statisk caching kan laves ved at lade .htaccess servere en gemt HTML-fil, hvis den findes. Hvis ikke, køres systemet som normalt, med den tilføjelse, at HTML-filen skrives, så de næste besøgende får gavn af den. Med andre ord køres der altså ikke en eneste linies PHP, når den statiske fil er lavet, den bliver bare serveret direkte.

At bruge denne metode er selvfølgelig ikke helt uden problemer. For det første skal ALT indhold på siderne være fuldstændig statisk. Der må ikke være noget, der er afhængigt af brugerens session. Og det er der jo i Magento, f.eks. indkøbskurven og produktsammenligning. For at bruge disse elementer skal de omskrives til AJAX, så de indlæses uafhængigt af sidens HTML. For det andet skal der tilføjes administrationsfunktioner til admin. I det mindste knapper til at slå statisk caching til og fra, og rense cachen. Automatisk sletning af dele af cachen når f.eks. produkter eller konfiguration opdateres, er nok for problematisk.

Mine foreløbige eksperimenter lover godt. Magento’s output kan fanges og gemmes med en observer og de gemte filer kan serveres lynhurtigt fra .htaccess. Jeg håber at få tid til at arbejde videre med dette, og vil fortsætte med at dele min erfaring her.

Dansk adresseformatering

Posted januar 15th, 2009 in Udvikling by Anders Rasmussen

Som standard formaterer Magento adresser på den amerikanske måde:

Varien
11832 W. Pico Blvd
Los Angeles, CA 90064
USA

I Danmark vil vi gerne have, at det ser sådan ud:

Crius
Skodsborgvej 190, 1, -2610
2850 Nærum
Denmark

Heldigvis kan dette let løses ved at indsætte danske formateringsregler i tabellen directory_country_format. Jeg har lavet et brugbart sæt af danske formateringsregler, så du kan bare køre følgende SQL:

INSERT INTO `directory_country_format` (`country_id`, `type`, `format`) VALUES
('DK', 'html', '{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}<br/>\r\n{{depend company}}{{var company}}<br />{{/depend}}\r\n{{var street1}}<br />\r\n{{depend street2}}{{var street2}}<br />{{/depend}}\r\n{{depend street3}}{{var street3}}<br />{{/depend}}\r\n{{depend street4}}{{var street4}}<br />{{/depend}}\r\n{{var postcode}} {{depend city}}{{var city}}{{/depend}}{{depend region}}, {{var region}}{{/depend}}<br/>\r\n{{var country}}<br/>\r\n{{depend telephone}}Tlf.: {{var telephone}}{{/depend}}\r\n{{depend fax}}<br/>Fax: {{var fax}}{{/depend}}'),
('DK', 'text', '{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}\r\n{{depend company}}{{var company}}{{/depend}}\r\n{{var street1}}\r\n{{depend street2}}{{var street2}}{{/depend}}\r\n{{depend street3}}{{var street3}}{{/depend}}\r\n{{depend street4}}{{var street4}}{{/depend}}\r\n{{depend city}}{{var city}},  {{/depend}}{{depend region}}{{var region}}, {{/depend}}{{var postcode}}\r\n{{var country}}\r\nT: {{var telephone}}\r\n{{depend fax}}F: {{var fax}}{{/depend}}'),
('DK', 'pdf', '{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}|\r\n{{depend company}}{{var company}}|{{/depend}}\r\n{{var street1}}|\r\n{{depend street2}}{{var street2}}|{{/depend}}\r\n{{depend street3}}{{var street3}}|{{/depend}}\r\n{{depend street4}}{{var street4}}|{{/depend}}\r\n{{var postcode}} {{depend city}}{{var city}}{{/depend}}{{depend region}}, {{var region}}, {{/depend}}|\r\n{{var country}}|\r\n{{depend telephone}}Tlf.: {{var telephone}}{{/depend}}|\r\n{{depend fax}}<br/>Fax: {{var fax}}{{/depend}}|'),
('DK', 'oneline', '{{depend prefix}}{{var prefix}} {{/depend}}{{var firstname}} {{depend middlename}}{{var middlename}} {{/depend}}{{var lastname}}{{depend suffix}} {{var suffix}}{{/depend}}, {{var street}}, {{var postcode}} {{var city}}, {{depend region}}{{var region}}, {{/depend}}{{var country}}'),
('DK', 'js_template', '#{prefix} #{firstname} #{middlename} #{lastname} #{suffix}<br/>#{company}<br/>#{street0}<br/>#{street1}<br/>#{street2}<br/>#{street3}<br/>#{postcode} #{city}, #{region}<br/>#{country_id}<br/>Tlf.: #{telephone}<br/>Fax: #{fax}');

Denne metode virker i Magento 1.1 og 1.2.

Dynamisk fordeling af elementer i en vandret menu

Posted november 30th, 2008 in Udvikling by Anders Rasmussen

Problemet: Du vil gerne have dine menupunkter i din vandrette menu fordelt pænt over hele bredden med samme afstand mellem hvert element. Menuen genereres dynamisk ud fra et CMS eller Magento, så du kan ikke regne ud på forhånd, hvor stor margin, der skal være.

Løsningen: Dette kan ikke umiddelbart løses med CSS alene, men bare rolig, det kan ordnes med hjælp fra et JavaScript framework. I dette eksempel vil jeg bruge Prototype, da det i forvejen bruges af Magento, men du kan også sagtens lave det med jQuery.

HTML

<ul id="topmenu-list">
<li><a href="#">Shop</a></li>
<li><a href="#">Kundeservice</a></li>
<li><a href="#">Handelsvilkår</a></li>
<li><a href="#">FAQ</a></li>
</ul>

JavaScript

<script language="javascript" type="text/javascript">
Event.observe(window, 'load', function() {
	menuItems = $('topmenu-list').childElements();
	var widthOfElements = 0;
	var totalWidth = 748; // Replace with total menu width
	// Calculate total width of elements
	for (var i = 0; i < menuItems.length; i++) {
		widthOfElements = widthOfElements + menuItems[i].getWidth();
	}
	// Calculate margin
	var menuMargin = Math.floor((totalWidth - widthOfElements) / (menuItems.length-1));
	// Set margin on elements
	for (var i = 0; i < menuItems.length-1; i++) {
		menuItems[i].setStyle({marginRight: menuMargin+'px'});
	}
});
</script>

Sådan. Elementernes bredde er udregnet, og margins er sat. Og det virker både i FF og IE 6+7.

Update 30-01-2009: Load-observer indsat i koden ovenfor, så det nu også virker i Safari.

Passwordbeskyttelse af shop

Posted oktober 16th, 2008 in Udvikling by Anders Rasmussen

Nogle B2B-shops har behov for at beskytte hele shoppen, så det kun er på forhånd oprettede kunder, der kan komme ind. Her vil jeg vise, hvordan det kan gøres ved at programmere et lille modul til formålet. Modulet skal hver gang en side tilgås kontrollere, om brugere er logget ind, og redirecte til login-siden, hvis login mangler.

I dette eksempel kaldes modulet MyCompany_Customer, og der derfor placeres en MyCompany_Customer.xml fil i app/etc/modules.

1. Konfiguration

app/etc/code/local/MyCompany/Customer/etc/config.xml:

<?xml version="1.0"?>
<config>
  <modules>
    <MyCompany_Customer>
      <version>0.1.0</version>
    </MyCompany_Customer>
  </modules>
  <global>
	<helpers>
		<mycustomer>
			<class>MyCompany_Customer_Helper</class>
		</mycustomer>
	</helpers>
  </global>
  <frontend>
	<events>
		<controller_action_layout_load_before>
			<observers>
				<MyCompany_Customer_observer>
					<type>singleton</type>
					<class>MyCompany_Customer_Helper_Observer</class>
					<method>checkLogin</method>
				</MyCompany_Customer_observer>
			</observers>
		</controller_action_layout_load_before>
	</events>
  </frontend>
</config>

Denne konfiguration tilføjer en såkaldt observer til Magento, som kaldes ved en bestemt event. I dette tilfælde er observeren vores egen funktion, som vi laver lige om lidt, og den vil blive kaldt før et layout indlæses.

2. Observer

app/code/local/MyCompany/Customer/Helper/Observer.php:

<?php
class MyCompany_Customer_Helper_Observer extends Mage_Core_Helper_Abstract
{
	public function checkLogin($observer)
	{
		$event = $observer->getEvent();
		$controller = $event->getAction();
		if (!Mage::getSingleton( 'customer/session' )->isLoggedIn() && $controller->getFullActionName() != 'customer_account_login' && $controller->getFullActionName() != 'customer_account_forgotpassword')
			$controller->getResponse()->setRedirect(Mage::getUrl('customer/account/login'));
		return $this;
	}
}

Her er så vores observer, som kontrollerer, om brugeren er logget ind, og hvis ikke dette er tilfældet redirectes til login-siden. Det er selvfølgelig også vigtigt at huske, at dette check ikke foretages på login-siden selv, da det jo ellers vil resultere i en uendelig redirect-løkke.

Teorien bag

Teknikken, jeg brugte her, var det såkaldte Observer pattern. Fordelen ved at bruge denne teknik er, at det ikke er nødvendigt at override Magento’s core-filer. Vi fortæller blot Magento, at vi gerne vil have vores funktion kaldt ved en bestemt hændelse, og det sørger Magento så for. Magento’s wiki har mere om event-observer metoden.

Kategorilayout baseret på kategoriniveau

Posted oktober 7th, 2008 in Udvikling by Anders Rasmussen

Dette er en lille tutorial, der viser, hvordan man kan tilføje sine egne layout-tags.

Til kategorier findes f.eks. layout-tags som <catalog_category_default> og <catalog_category_layered>, og i dette eksempel vil jeg tilføje tags til bestemte kategori-levels i kategori-hierarkiet. På den måde er det muligt at tilknytte layouts til kategorisiderne afhængig af, hvilket niveau, kategorien er på.

Trin 1: Override CategoryController

Opret en ny controller, der overskriver funktionen viewAction.

<?php
require_once 'Mage/Catalog/controllers/CategoryController.php';
class MyCompany_Catalog_CategoryController extends Mage_Catalog_CategoryController
{

    /**
     * Category view action
     */
 public function viewAction()
 {

     if ($category = $this->_initCatagory()) {

         Mage::getModel('catalog/design')->applyDesign($category, Mage_Catalog_Model_Design::APPLY_FOR_CATEGORY);
         Mage::getSingleton('catalog/session')->setLastViewedCategoryId($category->getId());

         $update = $this->getLayout()->getUpdate();
         $update->addHandle('default');

         if (!$category->hasChildren()) {
             $update->addHandle('catalog_category_layered_nochildren');
         }

         $this->addActionLayoutHandles();

         $update->addHandle($category->getLayoutUpdateHandle());
         $update->addHandle('CATEGORY_'.$category->getId());
         $update->addHandle('CATEGORY_LEVEL_'.$category->getLevel()); // added

         $this->loadLayoutUpdates();

         $update->addUpdate($category->getCustomLayoutUpdate());

         $this->generateLayoutXml()->generateLayoutBlocks();

         if ($root = $this->getLayout()->getBlock('root')) {
             $root->addBodyClass('categorypath-'.$category->getUrlPath())
                 ->addBodyClass('category-'.$category->getUrlKey());
         }

         $this->_initLayoutMessages('catalog/session');
         $this->_initLayoutMessages('checkout/session');
         $this->renderLayout();
     }
     else {
         $this->_forward('noRoute');
     }
 }
}

Eneste forskel fra den oprindelige viewAction er, at jeg har tilføjet en linje, der tilføjer et handle med navnet CATEGORY_LEVEL_X til layoutet, hvor X er det aktuelle level.

Trin 2: Tilføj layout

I catalog.xml kan du nu lave dine ønskede layout updates med de nye level handles. I det følgende eksempel definerer jeg specielle templates for niveau 2 og 3 kategorier:

    <CATEGORY_LEVEL_2>
        <remove name="category.products"/>
        <reference name="content">
            <block type="catalog/category_view" name="category.products" template="catalog/category/view/level2.phtml">
                <block type="catalog/product_list" name="product_list" template="catalog/product/list.phtml"/>
            </block>
        </reference>
    </CATEGORY_LEVEL_2>

    <CATEGORY_LEVEL_3>
        <remove name="category.products"/>
        <reference name="content">
            <block type="catalog/category_view" name="category.products" template="catalog/category/view/level3.phtml">
                <block type="catalog/product_list" name="product_list" template="catalog/product/list.phtml"/>
            </block>
        </reference>
    </CATEGORY_LEVEL_3>

Ønskeseddel til Magento

Posted september 23rd, 2008 in Udvikling by Anders Rasmussen

Magento 1.1 har allerede utroligt mange features, men det betyder ikke, at det ikke kan blive bedre. Her kommer derfor mine umiddelbare ønsker til vigtige funktioner:

  • Bedre performance. Magento er stadig et tungt system, og kører ikke godt på low-end servere.
    • Mere omfattende caching. Der må kunne laves noget statisk cache af blokke, som ikke har dynamisk indhold, sådan at det kun er f.eks. indkøbskurven der kommunikerer med systemet bagved.
  • Bedre CMS. Magentos nuværende CMS er faktisk bare et tekstfelt til at skrive nogle statiske tekster. Man kunne nok lære noget af systemer som Typo3 eller Joomla.
  • Deling af custom options. Custom options er guld værd, men det er et stort problem at man ikke kan definere nogle options som kan bruge af flere produkter, ligesom attribute sets. Desuden er det lidt forvirrende at have to koncepter, konfigurerbare produkter og custom options, der ligner hinanden så meget.
  • Mere import/eksport. I øjeblikket kan kun produkter og kunder importeres/eksporteres. Hvad med kategorier og ordrer/fakturaer?

Magento-udvikling: Funktioner der ikke eksisterer

Posted august 27th, 2008 in Udvikling by Anders Rasmussen

Denne artikel henvender sig til alle programmører, der er begyndere inden for Magento.

Er du begyndt at rode med Magentos kode er du måske allerede stødt på emnet for denne artikel: Funktioner, der ikke eksisterer. Mange steder støder man på funktionskald som $product->getDescription() eller $customer->setName('Anders Rasmussen'). Når man så kigger i klassens kode og går videre gennem nedarvningskæden finder man ud af, at disse funktioner slet ikke eksisterer.

Det er typisk her, man begyndt at rive sig i håret, for der må jo være noget man har overset. Man kan da ikke kalde en funktioner, der ikke eksisterer! Men jo, det kan man.

Svaret er, at Magento gør brug af en af PHP’s “magiske” funktioner, __call, i klassen Varien_Object, som er grundklassen for de fleste andre klasser. Hvis der kaldes en funktion, der ikke eksisterer, vil PHP i stedet forsøge at kalde __call, og medsender navnet på den ønskede funktion i dette kald. Dette udnytter Magento til at gette og sette data i objektet. Så når du kalder f.eks. getDescription eller setName, er det altså __call der behandler kaldet, og getter/setter data i et array. Dette er en stor fordel for Magentos ORM, idet getters og setters ikke behøver at skrives eksplicit i klasserne, de fås dynamisk via __call og databaseskemaet.

Tag et kig på funktionen i lib/Varien/Object.php.