Changing and Customizing Magento Code
If you’re finding that you need to make changes to Magento’s code to fit it into your organization, you’re probably wondering what is the best way to make these changes so that you can keep your code separate from the main code.
Skill level: Beginning to Advanced developer.
Target Audience: Developers who need to customize PHP Code.
Tested with Magento versions:
Applicable for all Magento versions (1.1.x and older).
The best way to keep track of all your changes is with a tool like Subversion (SVN) or CVS. You can use branches for the base Magento code. This will keep your customized files from being blatantly overwritten whenever you update Magento.
Here is an example code layout
my_project/ trunk/ scripts/ src/ (magento's code) data/ branches/ vendor/ magento/ app/ lib/ index.php
Under the “magento” directory, we have an export of Magento’s latest SVN (0.6 beta). Now, we can use SVN’s merge to copy the changes that happen to that one directory (branches/vendor/magento) into our main directory (trunk/src/). The first time, the changes will only be between revisions 1 and 2 (if don’t make any mistakes setting up the repository). But, every magento release, you will only have 1 revision change, for example: after making 100 changes to your store you are at revision 101. You update the Magento vendor branch with version 0.8 beta. Now you are at revision 102. And the difference between 101 and 102 contain all the Magento changes between 0.6.14400 and 0.8.2620 (imaginary magento numbers). So, when you merge 101:102 into your main/src directory, Subversion will do its best to only apply the changes, and not simply overwrite all your hard work with new files from 0.8.2620.
0.7.14800 was released yesterday. Here is the proper way to upgrade your own SVN repository if you’re using vendor branches as describe above.
Copy files over other files is not going to get you a clean upgrade. Subversion (and CVS) track updates, new files, and deletes. If you simply download and extract a new version, you’re going to miss key file deletes and config file deletes.
Dealing with vendor branches is very messy in SVN. The way you expect it to work doesn’t, so they provide you with an external Perl script to handle the messy parts of vendor branches. The program is called “svn_load_dirs.pl”, and, under Fedora, is located at /usr/share/doc/subversion-1.4.3/svn_load_dirs.pl
Our steps for upgrading the vendor branch go like this:
export a new copy of Magento 0.7
use svn_load_dirs to properly include the changes into our vendor branch.
In a clean directory:
svn export http://svn.magentocommerce.com/source/branches/beta-0.7-latest /usr/share/doc/subversion-1.4.3/svn_load_dirs.pl http://127.0.0.1/repos/my_project/branches/vendor magento beta-0.7-latest/
You should see some output like:
Checking that the base URL is a Subversion repository. Running /usr/bin/svn log -r HEAD http://127.0.0.1/repos/my_project/branches/vendor Finding the root URL of the Subversion repository. Running /usr/bin/svn log -r HEAD http://127.0.0.1 Running /usr/bin/svn log -r HEAD http://127.0.0.1/repos Running /usr/bin/svn log -r HEAD http://127.0.0.1/repos/my_project Determined that the svn root URL is http://127.0.0.1/repos/my_project. Native EOL on this system is �12. Finding if any directories need to be created in repository. .... The following table lists files and directories that exist in either the Subversion repository or the directory to be imported but not both. You now have the opportunity to match them up as renames instead of deletes and adds. This is a Good Thing as it'll make the repository take less space. The left column lists files and directories that exist in the Subversion repository and do not exist in the directory being imported. The right column lists files and directories that exist in the directory being imported. Match up a deleted item from the left column with an added item from the right column. Note the line numbers on the left which you type into this script to have a rename performed.
You will now see two columns of about 200 changes. On one side are files that are in the repository, but not in the new beta-0.7-latest directory, on the right files that exist in the new directory, but not in the repository. Subversion does not know which of these files have been added, deleted, or renamed. This is why vendor branches are messy. This Perl script wants you to scan through all 200 changes and match up files that actually got renamed or moved by typing in a number of a file in the right column and it’s match in the left, i.e. 214 10 if file 214 in the left column was actually just moved to a new directory and it shows up as row 10 in the right column. This task is summarily daunting and not worth the manual effort since we are only getting access to new Magento code about once every 800 changes.
“What does this mean to me?”, I hear you asking. Well, let’s say you’ve modified a file called “Google_Checkout.php”. In the Magento SVN repository, the Varien developers decide to rename the file, “GoogleCheckout.php”. This seemingly simple change can have dire consequences for you down the road. Since we cannot directly merge from Magento’s SVN repository, but we must use the intermediate files as a driver for “svn_load_dirs.pl”, this renaming action is lost. We simply have a new file “GoogleCheckout.php” and no more “Google_Checkout.php”. svn_load_dirs.pl, unless manually instructed, will delete the old file Google_Checkout and add the new one. When you go to merge the branch into your main development trunk, this add/delete action will be performed again, removing all your changes to the old named file “Google_Checkout”. “WHAT?!”, you ask, “Why do I even bother then with the vendor branches?”. The answer is, because it’s easier than not dealing with them.
Imagine if you simply copied over the new release and the newer “GoogleCheckout.php” file was being used instead of your changes. There would be no error, there would be no message from SVN deleting your code… your changes would silently be dropped simply because the file they were in would not be loaded anymore. At least with this process you will get alerted to your changes being removed, and you can manually pluck them out of an old version and re-instate your changes. Without this method, you will be left scratching your head wondering why so much stuff is behaving strangely after an upgrade.
Now you’re ready to see if all this extra work is worth it. It’s time to merge the vendor branch into your working changes. There is no need to have a physical disk checkout of your branch so you can go ahead and delete it.
cd /path/to/my_project/src/ svn merge --dry-run -r 71:72 http://127.0.0.1/repos/my_project/branches/vendor/magento
Assuming the auger goes for you, remove the –dry-run flag. More than likely, the auger will not go all well and you will see some SVN collisions. Even if you haven’t modified any of the code, the changes from one version of Magento to another may be too great for subversion to handle in one diff.
Now you have the option to study any of the collisions and upgrade at your leisure. This is far safer than simply overwriting a package full of files over your hard work.
To view all the collided files:
svn diff | grep ^C
Most likely you will want to make a module that represent’s your company to hold all your specific changes. Start off by making a new directory like so:
app/ code/ core/ community/ local/ XyzCorp/
Now, if you need to make changes to the Magento file, Mage/Catalog/Block/Breadcrumbs.php, you can add a new directory for the “Catalog” module under your XyzCorp directory, then a blocks directory and copy the file into this new directory. Also you need to create config.xml of your module.
app/ code/ core/ community/ local/ XyzCorp/ Catalog/ Block/ Breadcrumbs.php etc/ config.xml
Change the class name inside the file by starting off with “XyzCorp” instead of “Mage”.
Strip out all the code you don’t need, and make it subclass the original class name (”Mage_Catalog_Block_Breadcrumbs”).
Now, you must activate your module so Magento understands that there is new code in the “local” directory.
In app/etc/modules/XyzCorp_Catalog.xml, add the following lines
<?xml version="1.0"?> <config> <modules> <XyzCorp_Catalog> <active>true</active> <codePool>local</codePool> </XyzCorp_Catalog> </modules> </config>
It is crucial that the same prefix XyzCorp is used throughout files, class names, directories, and XML tag names.
Now, your own catalog module is activated, but when will it actually be called by the system? Ahh, we need a special rewrite tag to instruct Magento to use this one file (Breadcrumbs.php) instead of its default.
Now we should rewrite block using your module’s config file.
<?xml version="1.0"?> <config> <modules> <XyzCorp_Catalog> <version>0.1.0</version> </XyzCorp_Catalog> </modules> <global> <blocks> <catalog> <rewrite> <breadcrumbs>XyzCorp_Catalog_Block_Breadcrumbs</breadcrumbs> </rewrite> </catalog> </blocks> </global> </config>
We need to add a “blocks” tag, or edit inside an existing blocks tag, depending on your XML file. Then we add a rewrite tag after our module name, which is “catalog” in this case. Then, we throw in the word “breadcrumbs”. This “breadcrumbs” name must help magento to find the Block class you want to extend. In our example here, breadcrumbs is the core class file name (which will be overwritten): app/code/core/Mage/Catalog/Block/Breadcrumbs.php. If you have more levels below the Block directory, include it on that tag, using underscores to separate it from the class file name. Ex:
<blocks> <catalog> <rewrite> <category_view>XyzCorp_Catalog_Block_Category_View</category_view> </rewrite> </catalog> </blocks>
In this case, the class being overwritten is app/code/core/Mage/Catalog/Block/Category/View.php.
The data inside the breadcrumbs (same for category_view) tag is the name of your classfile, and Magento knows how to find it because the class name is the same as it’s directory path and filename. Remember that the underscore means another folder level on the file structure, and Magento won’t find your class in case the Folder structure doesn’t reflect the classname properly.
XyzCorp_Catalog_Block_Breadcrumbs → /app/code/local/XyzCorp/Catalog/Block/Breadcrumbs.php XyzCorp_Catalog_Block_Category_View → /app/code/local/XyzCorp/Catalog/Block/Category/View.php