Category Archives: PHP

Magento 2.0

News are coming out about our new loved/hated Magento major (and it’s gonna be really major) release: Magento2.

First of all a technical overview video, not too recent actually:

One amazing thing is that the development moved to github:
https://github.com/magento/magento2
with all the benefits we know.

Another nice improvement is that we have public tests for the test addicted:
https://github.com/magento/taf

A couple of nice articles about Magento 2.0:

Magento 2.0 is scheduled for Q4.2012, let’s see what is going to happen! :)

cygwin lighttpd php mysql, the perfect dev setup for windows

Since a few months, after more than 10 years on Linux, I switched my main OS to win7 (I’ll write something about this switch in a different post) and I’m quite happy with it, if there’s a thing I wasn’t happy about (’till today) was my development servers environment, I had Zend Server + MySQL, both as windows services, eating my pc’s resources also if I was not using it to work. Another thing was that I only work on Linux production servers and having those “c:\” paths just felt wrong.

I already used the great cygwin for a bunch of things, most of all rsync backups, so I decided to try removing Zend Server + MySQL for windows and move those services inside the cygwin environment, so I can start them with a bash script whenever I need them and I’m finally free from the “c:\” paths.

The problem was that there’s no documentation at all about this kind of installation, so:

  • no problems with MySQL, just install the packages, run “mysql_install_db” just the first time, and then call “mysqld_safe &” to start it when you need it
  • I couldn’t make apache2 work so I decided to use lighttpd
  • Configure and install all PHP packages you need using the cygwin ports project cause the main cygwin repository doesn’t have them

I ran into a few troubles configuring PHP for lighttpd, so here you have my /etc/lighttpd/lighttpd.conf:


# lighttpd configuration file
#
# use it as a base for lighttpd 1.0.0 and above
#
# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $

############ Options you really have to take care of ####################

## modules to load
# at least mod_access and mod_accesslog should be loaded
# all other module should only be loaded if really neccesary
# - saves some time
# - saves memory
server.modules = (
 "mod_rewrite",
# "mod_redirect",
# "mod_alias",
 "mod_access",
# "mod_cml",
# "mod_trigger_b4_dl",
# "mod_auth",
# "mod_status",
# "mod_setenv",
# "mod_fastcgi",
# "mod_proxy",
# "mod_simple_vhost",
# "mod_evhost",
# "mod_userdir",
 "mod_cgi",
# "mod_compress",
# "mod_ssi",
# "mod_usertrack",
# "mod_expire",
# "mod_secdownload",
# "mod_rrdtool",
 "mod_accesslog" )

## a static document-root, for virtual-hosting take look at the
## server.virtual-* options
server.document-root = "/srv/www/htdocs/"

## where to send error-messages to
server.errorlog = "/var/log/lighttpd/error.log"

# files to check for if .../ is requested
index-file.names = ( "index.php", "index.html",
 "index.htm", "default.htm" )

## set the event-handler (read the performance section in the manual)
# server.event-handler = "freebsd-kqueue" # needed on OS X

# mimetype mapping
mimetype.assign = (
 ".pdf" => "application/pdf",
 ".sig" => "application/pgp-signature",
 ".spl" => "application/futuresplash",
 ".class" => "application/octet-stream",
 ".ps" => "application/postscript",
 ".torrent" => "application/x-bittorrent",
 ".dvi" => "application/x-dvi",
 ".gz" => "application/x-gzip",
 ".pac" => "application/x-ns-proxy-autoconfig",
 ".swf" => "application/x-shockwave-flash",
 ".tar.gz" => "application/x-tgz",
 ".tgz" => "application/x-tgz",
 ".tar" => "application/x-tar",
 ".zip" => "application/zip",
 ".mp3" => "audio/mpeg",
 ".m3u" => "audio/x-mpegurl",
 ".wma" => "audio/x-ms-wma",
 ".wax" => "audio/x-ms-wax",
 ".ogg" => "application/ogg",
 ".wav" => "audio/x-wav",
 ".gif" => "image/gif",
 ".jar" => "application/x-java-archive",
 ".jpg" => "image/jpeg",
 ".jpeg" => "image/jpeg",
 ".png" => "image/png",
 ".xbm" => "image/x-xbitmap",
 ".xpm" => "image/x-xpixmap",
 ".xwd" => "image/x-xwindowdump",
 ".css" => "text/css",
 ".html" => "text/html",
 ".htm" => "text/html",
 ".js" => "text/javascript",
 ".asc" => "text/plain",
 ".c" => "text/plain",
 ".cpp" => "text/plain",
 ".log" => "text/plain",
 ".conf" => "text/plain",
 ".text" => "text/plain",
 ".txt" => "text/plain",
 ".dtd" => "text/xml",
 ".xml" => "text/xml",
 ".mpeg" => "video/mpeg",
 ".mpg" => "video/mpeg",
 ".mov" => "video/quicktime",
 ".qt" => "video/quicktime",
 ".avi" => "video/x-msvideo",
 ".asf" => "video/x-ms-asf",
 ".asx" => "video/x-ms-asf",
 ".wmv" => "video/x-ms-wmv",
 ".bz2" => "application/x-bzip",
 ".tbz" => "application/x-bzip-compressed-tar",
 ".tar.bz2" => "application/x-bzip-compressed-tar",
 # default mime type
 "" => "application/octet-stream",
 )

# Use the "Content-Type" extended attribute to obtain mime type if possible
#mimetype.use-xattr = "enable"
## send a different Server: header
## be nice and keep it at lighttpd
# server.tag = "lighttpd"

#### accesslog module
accesslog.filename = "/var/log/lighttpd/access.log"

## deny access the file-extensions
#
# ~ is for backupfiles from vi, emacs, joe, ...
# .inc is often used for code includes which should in general not be part
# of the document-root
url.access-deny = ( "~", ".inc" )

$HTTP["url"] =~ "\.pdf$" {
 server.range-requests = "disable"
}

##
# which extensions should not be handle via static-file transfer
#
# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )

###### virtual hosts

##
## If you want name-based virtual hosting add the next three settings and load
## mod_simple_vhost
##
## document-root =
## virtual-server-root + virtual-server-default-host + virtual-server-docroot
## or
## virtual-server-root + http-host + virtual-server-docroot
##
#simple-vhost.server-root = "/srv/www/vhosts/"
#simple-vhost.default-host = "www.example.org"
#simple-vhost.document-root = "/htdocs/"

## virtual directory listings

dir-listing.activate = "enable"
## select encoding for directory listings
dir-listing.encoding = "utf-8"

#

# chroot() to directory (default: no chroot() )
#server.chroot = "/"

## change uid to <uid> (default: don't care)
#server.username = "wwwrun"

## change uid to <uid> (default: don't care)
#server.groupname = "wwwrun"

cgi.assign = (".php"=>"/usr/bin/php-cgi")

Now that you’ve the right lighttpd config file, just start lighttpd with “/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf &” and you’re done, the perfect PHP dev environment on windows is yours.

Magento reindexer issue and very bad fix

On a big website, while Magento (this was a Professional 1.10.something) is reindexing (work done by a cron job) every page (viewed from the browser) seems to try to reindex the products it shows, that ends in a duplicate key error on an index temporary table (catalog_product_index_price_tmp), I needed a rough and quickfix I opened app\code\core\Mage\Index\Model\Resource\Helper\Abstract.php and added a try/catch to the insertFromSelect method, making it become:

public function insertFromSelect($select, $destTable, array $columns, $readToIndex = true)
    {
		try {
        if ($readToIndex) {
            $from   = $this->_getWriteAdapter();
            $to     = $this->_getIndexAdapter();
        } else {
            $from   = $this->_getIndexAdapter();
            $to     = $this->_getWriteAdapter();
        }

        if ($this->useDisableKeys()) {
            $to->disableTableKeys($destTable);
        }
        if ($from === $to) {
            $query = $select->insertFromSelect($destTable, $columns);
            $to->query($query);
        } else {
            $stmt = $from->query($select);
            $data = array();
            $counter = 0;
            while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
                $data[] = $row;
                $counter++;
                if ($counter>2000) {
                    $to->insertArray($destTable, $columns, $data);
                    $data = array();
                    $counter = 0;
                }
            }
            if (!empty($data)) {
                $to->insertArray($destTable, $columns, $data);
            }
        }
        if ($this->useDisableKeys()) {
            $to->enableTableKeys($destTable);
        }
		} catch (Exception $e) {}
        return $this;
    }

It is bad but now it’s working, oh god.

PHP 5.4, Zend Framework 2, Zend Studio 9

A lot of things are moving in the PHP ecosystem, but it seems to me that those things are going a little bit out of control and I’ve a few philosophical questions…

PHP 5.4

Hey great, we’ve traits and a built-in web server. Hemmm what about the damn UTF8 support? It’s 2012 and still a substr can split a character in half! And I don’t think the whole world is still using ANSI or ISO-8859, everyone now uses UTF8 and working with UTF8 inside PHP is always a pain. Traits and that web server were really more important?

At least we’ve a useful new feature: array dereferencing, something python had since maybe 10 years (along with the utf8 support…). I’m not telling python is better otherwise I won’t be using 99% PHP since 12 years.

Zend Framework 2

I hated pretty much every MVC framework out there but ZF is really much more than just an MVC framework (also if not so many people can see that). I used its MVC only a little so I won’t talk about that but I loved the entire ZF1 project, it was just perfect, really easy to use, simple code so the documentation is something useless and it was also really easy to include a single element in your project. That was great and it’s ended.

ZF2 is surely a great software but the devs seems not to remember that PHP was born with simplicity in mind and ZF2 is millions light years beyond simplicity:

  • you won’t ever be able to use a single component (let’s say you just want to use zend\db) without having to deal with the autoloader
  • today we’ve ZF2 beta3, I always knew that “beta” meant “API freeze” and instead from beta2 to beta3 something like EVERYTHING changed! I was writing some code using Zend\Db and I had to throw it all in the garbage because Zend\Db was completely rewritten for beta3. This make me think of a pre-alpha, not a beta.
  • ZF2 is a complete rewrite of ZF1 and thus I bet it will be a hell of bugs for a long long time, in fact I just started using it in real life and I found 2 blocking bugs (blocking for me obvious), one about Zend\Db (unable not to quote values) and one about Zend\Locale (unable to get territory list), both were perfectly working in ZF1
  • ZF bug tracker is like the most silent western movie, you can’t ever hear a voice, that’s just a bit disappointing
  • ZF2 code is so complex that it’s nearly impossible to go through it just to find how a method works so if there won’t be a really great documentation it will really end in a developing pain

Keeping in mind all of the above, would you go back to ZF1 for your new project knowing that in 18 months you’ll have to completely rewrite everything ’cause ZF1 will be out of support? The answer is no but we’re stuck in a limbo.

And a question for the developers, do you really think that pushing things so high about complexity is a good thing for the success of ZF? I’m not so sure about that. ZF1 could be used by every junior dev out there, I wouldn’t say that for ZF2.

Zend Studio 9

At the moment Zend Studio is IHMO the most updated (9.0.2 already supports PHP 5.4) and feature rich PHP IDE but I still can’t understand if the devs are actually using it, just a few examples:

  • Most of the time I’m on ZS it compiles, indexes and refreshes the workspace, completely eating my cpu and sometimes preventing me from saving a file until the indexing process is finished.
  • GIT is widely used now that really big PHP projects are all over the world (anyone said Magento?) but the GIT plugin for ZS is incomplete and caused me some big problem in the testing phase. One feature missing is the support for GIT submodule, but there is not a single project I’ve ever worked on that didn’t have submodule => I’ve to use an external GIT client and this really bothers me

Probably those problems are Eclipse’s problems but that’s not the point.

Conclusions

It’s a long way to the top…

Programmatically delete all Magento attribute sets

You’re developing a Magento importer and you want to clean your DB after some tests? Here you have:

require_once dirname(__FILE__) . '/app/Mage.php';
Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));

$resource = Mage::getSingleton('core/resource');
$db_read = $resource->getConnection('core_read');

$attribute_sets = $db_read->fetchCol("SELECT attribute_set_id FROM " . $resource->getTableName("eav_attribute_set") . " WHERE attribute_set_id<> 4 AND entity_type_id=4");
foreach ($attribute_sets as $attribute_set_id) {
	try {
		Mage::getModel("eav/entity_attribute_set")->load($attribute_set_id)->delete();
	} catch (Exception $e) {
		echo $e->getMessage() . "\n";
	}
}

Note that only products’ attribute sets are deleted, those are often generated automatically during imports.

Programmatically delete all Magento categories

You’re developing a Magento importer and you want to clean your DB after some tests? Here you have:

require_once dirname(__FILE__) . '/app/Mage.php';
Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));

$resource = Mage::getSingleton('core/resource');
$db_read = $resource->getConnection('core_read');

$categories = $db_read->fetchCol("SELECT entity_id FROM " . $resource->getTableName("catalog_category_entity") . " WHERE entity_id>1 ORDER BY entity_id DESC");
foreach ($categories as $category_id) {
	try {
		Mage::getModel("catalog/category")->load($category_id)->delete();
	} catch (Exception $e) {
		echo $e->getMessage() . "\n";
	}
}

Create a custom category attribute in Magento

I’ve run into a few posts about this thing but none of them was working out of the box so I took all the info and glued them together so…

if you’ve to create a custom attribute for a Magento category but you don’t have a module (and its installer script) simply create a php file in the project’s root with this code:

require_once('app/Mage.php');
Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));
$installer = new Mage_Sales_Model_Mysql4_Setup;
$attribute  = array(
	'type' => 'int',
	'label'=> 'Your attribute label',
	'input' => 'text',
	'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
	'visible' => true,
	'required' => false,
	'user_defined' => true,
	'default' => "",
	'group' => "General Information"
);
$installer->addAttribute('catalog_category', 'your_attribute_code', $attribute);
$installer->endSetup();

This attribute is a “text”, more info may come in comments if you need.

Magento Professional customer’s password hashing

So you want to import customer accounts but you’ve to generate the password hash? here you’ve the algorithm used by Magento Professional (which is different from Magento Community, but you can easily find that one in the net):

$passhash = hash("sha256", $salt . $password) . ":$salt";

By the way, for the community it’s

$passhash = md5($salt . $password) . ":$salt";

Create a custom order attribute in Magento

I’ve run into a few posts about this thing but none of them was working out of the box so I took all the info and glued them together so…

if you’ve to create a custom attribute for a Magento order but you don’t have a module (and its installer script) simply create a php file in the project’s root with this code:

require_once('app/Mage.php');
Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));
$installer = new Mage_Sales_Model_Mysql4_Setup;
$attribute  = array(
        'type'          => 'text',
        'backend_type'  => 'text',
        'frontend_input' => 'text',
        'is_user_defined' => true,
        'label'         => 'Your attribute label',
        'visible'       => true,
        'required'      => false,
        'user_defined'  => false,   
        'searchable'    => false,
        'filterable'    => false,
        'comparable'    => false,
        'default'       => ''
);
$installer->addAttribute('order', 'your_attribute_code', $attribute);
$installer->endSetup();

This attribute is a “text” and it’s made not to be visible but just to use it as a container for some data you may need in your custom development, more info may come in comments if you need.