30
votes

What is the preferred method of beginning a full site build, from scratch? My builds typically begin from fully mocked-up HTML5 wireframe pages, and we plug in functionality from there.

But, part of me feels that most of the time in the late stages of a project we wind up finding all of the areas of functionality that need to be plugged in - typically things like Enterprise RMA, Customer My Account section, etc. I think this could be avoided by starting with a Magento theme from day 1; the opposing view says that there will be equal amount of time writing CSS and modifying structure from the default.

Do you build on the Core "blank" theme? Is there an Enterprise version of this blank theme? What is the best-practice here?

32
votes

So this will cause ultimate uproar and go against the grain of every Magento developer - but we've got a solid process for theming - that does not use local.xml (more on that later).

We always work off the base/default (and enterprise/default for EE) template - but zero out the CSS. Even though all designs don't particularly lend themselves to the structural layout of a vanilla Magento store - we find it good practice to use the default theme as a starting point; we can remove un-used methods/loops/html etc. as necessary during the templating.

When starting a theme

For EE

We install this extension first, so that we get a level of theme fallback - when we later remove the theme files we copied.

The package

We first start by creating the package and copying in the entire base/default theme; so for example (say it was our own website, we'd call the package sonassi)

cd ./app/design/frontend
mkdir sonassi
cp -par base/default sonassi/default
mkdir sonassi/default/layout/custom

The template

The ultimate goal is that we don't have to keep copy and pasting each file that we modify whenever we need to, we just edit the file in the theme.

But each time we edit the file, we strip out the Magento Commerce headers - and add an appropriate header/identifier to mark the file as being a custom template, usually something like ...

/*
* @category    Template
* @package     sonassi_default
* @copyright   Copyright (c) 2013 Sonassi
*/

This header serves a purpose later when we do the final template clean-up. As we will do a recursive diff on the base/default/template directory and the sonassi/default/template directory - then delete anything that hasn't been changed.

This way, only the modified files remain and the overall package has been reduced to the minimum changed files.

The layout files

We use a standard core module of our own sonassi.core. Yes, we always prefix the module namespace with a unique identifier - it stops conflicts where other companies have picked the same name (eg. fishpig/wordpress and sonassi/wordpress)


The nolocal layout methodology

<core>
  <rewrite>
    <layout>Sonassi_Core_Model_Core_Layout</layout>
    <layout_update>Sonassi_Core_Model_Core_Layout_Update</layout_update>
  </rewrite>
</core> 

Then the two magic classes that add the functionality to never need local.xml again,

class Sonassi_Core_Model_Core_Layout 
    extends Mage_Core_Model_Layout
{
    /**
     * Loyout xml generation
     *
     * @return Mage_Core_Model_Layout
     */
    public function generateXml()
    {
        $xml = $this->getUpdate()->asSimplexml();
        $removeInstructions = $xml->xpath("//remove");
        if (is_array($removeInstructions)) {
            foreach ($removeInstructions as $infoNode) {
                $attributes = $infoNode->attributes();
                $blockName = (string)$attributes->name;
                if ($blockName) {
                    $unremoveNodes = $xml->xpath("//unremove[@name='".$blockName."']");
                    if (is_array($unremoveNodes) && count($unremoveNodes) > 0) {
                        continue;
                    }
                    $ignoreNodes = $xml->xpath("//block[@name='".$blockName."']");
                    if (!is_array($ignoreNodes)) {
                        continue;
                    }
                    $ignoreReferences = $xml->xpath("//reference[@name='".$blockName."']");
                    if (is_array($ignoreReferences)) {
                        $ignoreNodes = array_merge($ignoreNodes, $ignoreReferences);
                    }

                    foreach ($ignoreNodes as $block) {
                        if ($block->getAttribute('ignore') !== null) {
                            continue;
                        }
                        $acl = (string)$attributes->acl;
                        if ($acl && Mage::getSingleton('admin/session')->isAllowed($acl)) {
                            continue;
                        }
                        if (!isset($block->attributes()->ignore)) {
                            $block->addAttribute('ignore', true);
                        }
                    }
                }
            }
        }
        $this->setXml($xml);
        return $this;
    }
}

and

class Sonassi_Core_Model_Core_Layout_Update 
    extends Mage_Core_Model_Layout_Update
{

    public function getFileLayoutUpdatesXml($area, $package, $theme, $storeId = null)
    {
        if (null === $storeId) {
            $storeId = Mage::app()->getStore()->getId();
        }
        /* @var $design Mage_Core_Model_Design_Package */
        $design = Mage::getSingleton('core/design_package');
        $layoutXml = null;
        $elementClass = $this->getElementClass();
        $updatesRoot = Mage::app()->getConfig()->getNode($area.'/layout/updates');
        Mage::dispatchEvent('core_layout_update_updates_get_after', array('updates' => $updatesRoot));
        $updateFiles = array();
        foreach ($updatesRoot->children() as $updateNode) {
            if ($updateNode->file) {
                $module = $updateNode->getAttribute('module');
                if ($module && Mage::getStoreConfigFlag('advanced/modules_disable_output/' . $module, $storeId)) {
                    continue;
                }
                $updateFiles[] = (string)$updateNode->file;

                // custom theme XML contents
                $updateFiles[] = 'custom/'.(string)$updateNode->file;    

                // custom theme XML override
                $updateFiles[] = 'local/'.(string)$updateNode->file;            
            }
        }

        // custom local layout updates file - load always last
        $updateFiles[] = 'local.xml';
        $layoutStr = '';
        foreach ($updateFiles as $file) {
            $filename = $design->getLayoutFilename($file, array(
                '_area'    => $area,
                '_package' => $package,
                '_theme'   => $theme
            ));
            if (!is_readable($filename)) {
                continue;
            }
            $fileStr = file_get_contents($filename);
            $fileStr = str_replace($this->_subst['from'], $this->_subst['to'], $fileStr);
            $fileXml = simplexml_load_string($fileStr, $elementClass);
            if (!$fileXml instanceof SimpleXMLElement) {
                continue;
            }
            $layoutStr .= $fileXml->innerXml();
        }
        $layoutXml = simplexml_load_string('<layouts>'.$layoutStr.'</layouts>', $elementClass);
        return $layoutXml;
    }

}

The two above classes add the functionality into Magento so that you can extend - but not overwrite a layout XML file. Extensibility of the layout XML is important for us, as it allows us to still maintain the same file separation catalog.xml, cms.xml etc. - but only need to add short portions of layout XML to manipulate blocks (insert/clone/remove).

The local.xml methodology is that you just enter your overriding changes into one cumbersome unmanageable file.

The nolocal methodology means that rather than putting all changes in a single file, you put them in a file with the appropriate filename that it is modifying (eg. catalog.xml) - by simply creating a new file sonassi/default/layout/custom/catalog.xml - with *only the modifications.

Again, once we're done making the template, we can just remove the contents of sonassi/default/layout with the exception of the custom directory. This way again, like with the template, we have a lightweight extended template - based off the base templates.

The style sheets

We delete them, all of them. We don't bother copying them into our package's CSS directory. We'll copy in the JS and that is it - the images and CSS directory will be empty from the start.

As we're using SASS nowadays, we'll have another directory (scss) for the pre-processed CSS - and output to the main styles/print CSS file(s).

Cleaning up the template

So as we mentioned, once the template theme is completed, you can now clean it up - to remove the un-modified files and reduce it down to the bare minimum.

cd ./app/design/frontend

PREFIX="cleantheme_"
THEME="sonassi/default"
diff -BPqr "base/default/template" "$THEME/template" | \
awk '{print $4}' | \
  while read FILE; do 
    DIR=$(dirname "$FILE")
    [ -d "$PREFIX$DIR" ] || mkdir -p "$PREFIX$DIR"
    [ -f "$PREFIX$FILE" ] || cp -pa "$FILE" "$PREFIX$FILE"
  done
cp -par "$THEME/layout" "$PREFIX$THEME/"

So why no local.xml?

Its not for you - its for 3rd parties, the same way that community is for you and local is for 3rd parties. Its a failback, last resort, final destination for overrides.

Structuring the XML in this fashion keeps it in-line with the way Magento originally configured the directory and file structure. Plus, for continuity of development - it just makes more sense, is a lot easier to digest and doesn't add noticeable overhead.

Magento is an odd product, the community has invented its own best-practice based on common sense and mimicking what the Magento core team do. So there isn't ever an official way (not until a unicorn rides in with the Magento-1 documentation); but this is our way.

So I'd even stretch to say this isn't the answer, its just one of many ways to tackle a commonly faced challenge. Although I'd like to think our method is the best.

Content happily sourced from sonassi.com

  • 1
    You guys are nothing if not incredibly creative. – philwinkle May 16 '13 at 15:55
  • 3
    Very nice write-up and solid advice all-around. – ColinM May 16 '13 at 17:56
  • I like your approach to extending layout xml rather than just copying across existing files or using local.xml, that's awesome! Instead of copying across all of the template files initially, I love Magicento's "Copy Template" command, which can be used to copy a template to another theme easily, placing it in the right folder structure. Alternatively, when cleaning up the3 theme at the end, you might want to check out n98-magerun.phar's "dev:theme:duplicates" command, which will produce a list of files which haven't been changed. – Jim OHalloran May 20 '13 at 4:27
  • I wonder if there is a plugin that fallbacks into default/default for CE installs. Perhaps not that hard to hack with the given sources, just curious (chewing on this: Magento Theme Fall-back / Hierarchy in the light of custom theme and third-party extensions) – hakre May 7 '14 at 13:40
  • "...the same way that community is for you and local is for 3rd parties..." The Magento way is for this to be the other way around as stated here: magentocommerce.com/wiki/2_-_magento_concepts_and_architecture/… – RichardBernards Nov 28 '14 at 12:17
7
votes

Build a blank bootstrap theme for Enterprise. That means taking the enterprise/default theme, cleaning up its CSS, and "clicking all the things" to verify that you've handled styling the features. Don't forget about the product grid view voodoo.

One of the benefits is that this affords the opportunity to setup a LESS (or other) workflow. Think about it - while the blank theme is a nice start for light-colored themes, it's a bit of work to switch it to adapt to a dark/black theme. Above all, you MUST incorporate the enterprise/default theme, otherwise you have a broken EE install from the start.

  • You're absolutely right – philwinkle May 16 '13 at 14:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.