Category: 小小草

IT 技术领域学海无涯。其实任何领域都学海无涯,无非 IT 发展太快了,让我有更多嘘唏。希望我掌握的技术有如小小草,虽然渺小,却有旺盛的生命力。

  • Magento extension: rcah 0.1.1 is released

    Magento store owners are having a challenge when using rcah 0.1.0, especially for homepage. The nature of homepage is CMS, most content of it static.

    Store owners want their visitors (especially first time visitors) to read static content on the homepage, but static content become obstacles filling in the central area of the page when visitors start to click on filters for refined results.

    Root category as homepage 0.1.1 is released to address the problem. It conditionally hides the CMS Block on category pages. It only hides CMS Block in Display Settings of a category (category landing_page attribute). Static CMS Blocks used for others purposes (in sidebar, footer, etc) are not affected.

    I must say the conditions when hiding the CMS Block are subtle. Please refer to readme file with the release for details.

    Download RootCategoryAsHomepage.tar.gz

  • When does php apc refresh cache data?

    I did this experiment with php apc:

    In php.ini I set include_path = /first/include/path:/second/include/path, but do not put a file in /first/include/path or /second/include/path, create test.php as simple as this

    <?php
    require 'tobeincluded.php';
    

    It will run into a fatal error. This error is obvious.

    Then I create the file tobeincluded.php in /second/include/path, test.php now runs all right.

    Then I create another file with same file name as tobeincluded.php but different content and put it in /first/include/path, test.php keeps running as if /first/include/path/tobeincluded.php does not exist. This is because php apc has cached /second/include/path/tobeincluded.php and ignored /first/include/path/tobeincluded.php. Apc will refresh its cache when it is restarted or original cached file is updated in the file system. I assume apc is checking file timestamp.

    Then I copy test.php to test2.php and put test2.php in the same folder as test.php, run test2.php. It renders the same result as test.php – include /second/include/path/tobeincluded.php but ignore the existence of /first/include/path/tobeincluded.php.

    Then I copy test.php to test3.php and put test3.php in a different folder (no matter parent folder or child folder or irrelevant folder), run test3.php. Now it is aware of the existence of /first/include/path/tobeincluded.php so /first/include/path/tobeincluded.php is included in test3.php.

    Even I trigger to run test2.php in a different virtual host (but same document root), it still renders the same result. I assume php file on the file system is identical to apc. In other words, apc loads cache data by realpath regardless virtual host.

  • Magento does not remove storeview data when scope changes

    今天我做了个有趣的实验:

    1. 首先 Manage Attributes,设置某 product attribute ‘my_attr’ scope 为 Store view
    2. 然后 Manage Products,为 my_attr 设置 default value ‘aaa’,再设置 store view (假设 storeId = 2) value ‘bbb’
    3. 然后 Manage Attributes, 把 my_attr scope 改为 Global
    4. 然后 Manage Products,Choose Store View (storeId = 2),这时仍可见 my_attr 显示值 ‘bbb’。It means although my_attr scope is changed to ‘Global’, but old store view specific values are not removed.
    5. 这时 (my_attr scope is Global) 如果在 Store View (storeId = 2) 状态把 my_attr 的值改为 ‘ccc’,then Save Product。my_attr default value 被改为 ‘ccc’,store view (storeId = 2) value 仍为 ‘bbb’。It means when my_attr scope is ‘Global’, it is not possible using Manage Products GUI to change or remove existing store view value.

    摸清 Magento 的规律后就容易理解 Manage Products GUI 在 scope 改变或 store view 切换时的行为。另文有提及 change product default value and store view view by programming.

  • Class Mage_Sales_Model_Entity is deprecated

    今天终于搞清楚了一件困扰我已久的问题:在 Magento Sales Module 下有两个分支 Mage_Sales_Model_Entity 和 class Mage_Sales_Model_Mysql4,看上去文件结构比较相似,Sales 和其他模块一时用 Mage_Sales_Model_Entity,一时又用 Mage_Sales_Model_Mysql4。它们有什么区别?为什么要同时存在?有必要同时存在吗?如要删除一个,该删哪个保留哪个?

    以前有个任务,想要给 order 等 entity 添加一些自定义属性,因为搞不清楚哪个在起作用,我把两个类都 extends 了,即 class Mage_Sales_Model_Entity_Setup 和 class Mage_Sales_Model_Mysql4_Setup。而因为其他一些原因,自定义属性并不能完全按照我的意愿工作,所以我也没办法再用替换法去测试哪个类在起作用。

    今天重新回顾了这个问题,摸清 Mage_Sales_Model_Entity is deprecated。When extending sales entity, only need to extend classes in Mage_Sales_Model_Mysql4.

    但是简单地删除 magento/app/code/core/Mage/Sales/Model/Entity 目录则会导致程序找不到必要的文件而出错。我的建议是了解 Mage_Sales_Model_Entity is deprecated 这个事实就可以了,如果一定想割了这个阑尾目录,得做一些调整:

    • 搜索一下哪些 class extends Entity 下的类 (search for Mage_Sales_Model_Entity under magento folder),把它们改成 extends Mysql4 下对应的类。据我搜索结果,只有 Mage_Reports 一些 class extends Entity 下的类,或许 Mage_Sales 在 Mage_Reports 成形之后进行 Entity to Mysql4 的调整,而为了省事,保留 Entity 下的类,就不用去修改 Mage_Reports 了。
    • 搜索一下哪些地方用到了 getModel(‘sales_entity/entity_name’) 的方式,把它们改成 getModel(‘sales_mysql4/entity_name’)。这需要同时搜索文件和数据库(数据库里 eav_attribute 有几个 backend models 是这样被引用的)。如果嫌这么替换比较麻烦,也可以修改 magento/app/code/core/Mage/Sales/etc/config.xml,把 <sales_entity><class>Mage_Sales_Model_Entity</class></sales_entity> 修改成 <sales_entity><class>Mage_Sales_Model_Mysql4</class></sales_entity>,这样就把 sales_entity model 与 sales_mysql4 model 等价起来了。
  • Google Contacts synchronisation fails on my Android after Google Mail became Gmail

    Google announced Google Mail is becoming Gmail in the UK in May. Then some day I decided to swap over my Google Account primary email address from @googlemail.com to @gmail.com. Everything continues to work as before. It should be working because I assume googlemail.com or gmail.com is alias to the other.

    After quite a long time (at least 2 weeks), I noticed Google Contacts stopped synchronisation from 28 May, 2008. Because Google Mail and Google Calendar continued to work all right, so I did not think it was problem of my Google Account change. I did not regard the domain name swap was a change. I thought it was merely Google was updating Google Contacts programme or server something.

    I have been waiting for Google Contacts synchronisation back to normal for a month. During the month I googled quite often if someone else encountered the same problem, but I did not find a case like mine.

    I could not live up with a situation that I had to enter a new contact twice any longer, so I started investigating harder. And only by change I realised it might be a problem caused by domain name swap. Then how to change Google Account information stored on my Android? I find it is not straight forward.

    First there is no interface for change the Google Account on Android. Someone mentioned a tricky method by changing the Google Account password then reboot Android. But it did not work on my Android G1. After reboot, it only pop up a box to enter password, not for account username.

    I manage to change Google Account by clicking Setup -> Applications -> Manage applications -> Google Apps -> Clear data, then reboot. Clear Google Apps data clear all contacts on Android as well, but it is not a problem for me because I have all contacts in google cloud anyway.

    A by-product of my investigation is I find Android support Google Apps Account as normal Google Account, which makes me very happy because I use Google Apps Account much more than Google Account.

  • Upgrade zend server php 5.2 to 5.3

    The correct command to remove php 5.2 before upgrade zend server to 5.3 is

    yum -y remove zend-server-php-5.2 && yum -y remove `rpm -qa|grep zend|xargs`

    Use yum -y remove zend-server-php-5.2 is not enough.

    After removal, run

    yum install zend-server-ce-php-5.3

    Of course, zend repository must be add to /etc/yum.repos.d before. (If not, where you got zend server php 5.2?)

    The most recent /etc/yum.repos.d/zend.repo is

    [Zend]
    name=Zend Server
    baseurl=http://repos.zend.com/zend-server/rpm/$basearch
    enabled=1
    gpgcheck=0

    [Zend_noarch]
    name=Zend Server – noarch
    baseurl=http://repos.zend.com/zend-server/rpm/noarch
    enabled=1
    gpgcheck=0

    I notice it was changed since my first copy. It was

    [Zend]
    name=Zend CE $releasever – $basearch – Released Updates
    baseurl=http://repos.zend.com/rpm/ce/$basearch/
    enabled=1
    gpgcheck=0
    [Zendce-noarch]
    name=Zend CE – noarch
    baseurl=http://repos.zend.com/rpm/ce/noarch
    enabled=1
    gpgcheck=0

    Old copy no long work with command
    yum install zend-server-ce-php-5.3

  • 1and1 cloud server datasheet

    从 1&1 新订了一个合同,cloud server,就是为了让 magento 跑快一些。那 1and1 的 cloud server 究竟能有多快呢?

    先看看 cat /proc/cpuinfo 的情况
    processor : 0
    vendor_id : AuthenticAMD
    cpu family : 16
    model : 2
    model name : Quad-Core AMD Opteron(tm) Processor 2352
    stepping : 3
    cpu MHz : 2109.718
    cache size : 512 KB
    fpu : yes
    fpu_exception : yes
    cpuid level : 4
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
    bogomips : 4219.43
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 48 bits physical, 48 bits virtual
    power management:

    processor : 1
    vendor_id : AuthenticAMD
    cpu family : 16
    model : 2
    model name : Quad-Core AMD Opteron(tm) Processor 2352
    stepping : 3
    cpu MHz : 2109.718
    cache size : 512 KB
    fpu : yes
    fpu_exception : yes
    cpuid level : 4
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
    bogomips : 4220.53
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 48 bits physical, 48 bits virtual
    power management:

    processor : 2
    vendor_id : AuthenticAMD
    cpu family : 16
    model : 2
    model name : Quad-Core AMD Opteron(tm) Processor 2352
    stepping : 3
    cpu MHz : 2109.718
    cache size : 512 KB
    fpu : yes
    fpu_exception : yes
    cpuid level : 4
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
    bogomips : 4223.97
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 48 bits physical, 48 bits virtual
    power management:

    processor : 3
    vendor_id : AuthenticAMD
    cpu family : 16
    model : 2
    model name : Quad-Core AMD Opteron(tm) Processor 2352
    stepping : 3
    cpu MHz : 2109.718
    cache size : 512 KB
    fpu : yes
    fpu_exception : yes
    cpuid level : 4
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush mmx fxsr sse sse2 syscall mmxext fxsr_opt lm 3dnowext 3dnow pni cx16 popcnt lahf_lm cr8_legacy altmovcr8 abm sse4a misalignsse
    bogomips : 4219.25
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 48 bits physical, 48 bits virtual
    power management:

    在 cloud server 上我使用 zend server。我特意从 1 Virtual processor core 1GB Ram 一步步往上加,使用 ab -c 5 -n 500 对比测试,客户端是 10 Mb down stream / 1Mb up stream adsl 连接。

    在 1 Virtual processor core 1GB Ram 时,Requests per second: 5.31 [#/sec]
    在 2 Virtual processor cores 1GB Ram 时,Requests per second: 8.34 [#/sec]
    在 2 Virtual processor cores 2GB Ram 时,Requests per second: 8.26 [#/sec]
    在 3 Virtual processor cores 1GB Ram 时,Requests per second: 10.57 [#/sec]
    在 4 Virtual processor cores 1GB Ram 时,Requests per second: 11.33 [#/sec]

    在 shopping cart 里有 7 条不同商品时,checkout/cart/index 页面时间为 10 秒左右。

    回头看看老 server
    # cat /proc/cpuinfo
    processor : 0
    vendor_id : AuthenticAMD
    cpu family : 15
    model : 67
    model name : Dual-Core AMD Opteron(tm) Processor 1216 HE
    stepping : 3
    cpu MHz : 1000.000
    cache size : 1024 KB
    physical id : 0
    siblings : 2
    core id : 0
    cpu cores : 2
    apicid : 0
    initial apicid : 0
    fpu : yes
    fpu_exception : yes
    cpuid level : 1
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
    bogomips : 1999.96
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 40 bits physical, 48 bits virtual
    power management: ts fid vid ttp tm stc

    processor : 1
    vendor_id : AuthenticAMD
    cpu family : 15
    model : 67
    model name : Dual-Core AMD Opteron(tm) Processor 1216 HE
    stepping : 3
    cpu MHz : 1000.000
    cache size : 1024 KB
    physical id : 0
    siblings : 2
    core id : 1
    cpu cores : 2
    apicid : 1
    initial apicid : 1
    fpu : yes
    fpu_exception : yes
    cpuid level : 1
    wp : yes
    flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good nopl pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
    bogomips : 1999.96
    TLB size : 1024 4K pages
    clflush size : 64
    cache_alignment : 64
    address sizes : 40 bits physical, 48 bits virtual
    power management: ts fid vid ttp tm stc

    运行的是 nginx,同等条件下,Requests per second: 8.12 [#/sec],checkout/cart/index 页面生成时间也是 10 秒左右。

    虽 zend server 和 nginx 不同,靠不是完全对等的对比结果,我还是可以得出两个结论:

    1. 瓶颈仍是 cpu 速度;
    2. cloud server 并未显著提高速度,我对它预期过高,略有失望。
  • How to detect current connection is secure using Php?

    Over a long time I had believed there was a function or an environment variable I could use in Php to tell whether the current connection secure or not, i.e. a page requested via http or https protocol. Unfortunately I could not find a good solution. I can not find it does not mean it does not exist.

    Recently I intend to believe the perfect solution does not exist, i.e. I can not write something in Php safely to tell whether the current connection is secure unless I know the server setting.

    Look at the code snippet Magento use for isSecure function:

    $secure = (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!='off')) || $_SERVER['SERVER_PORT']=='443';
    

    This code is not perfect because it assumes the server is not listening http on 443 AND server is listening HTTPS on 443.

    Unfortunately I can not do more than that. I am wondering whether the situation is the same in Python or Java.

  • Clean temporary files left by editors

    打开文件进行编辑或其他操作后,有时会留下一些临时文件。按理只有非正常中止会留下临时文件,但有时即使正常退出仍在硬盘上留下一堆垃圾,可能某些编辑器有 bug。

    不过,清理临时文件也不费力,找准文件名特征可以一次性删除它们。

    find /var/www -name ".goutputstream-*" -exec rm -rf {} \;

    据说 .goutputstream-* 是 GNOME 拷贝粘帖文件时的临时文件。

    find /var/www -name ".tmp*~" -exec rm -rf {} \;

    .tmp*~ 是 Text Editor 编辑时的临时文件。

    保持良好的习惯,不要用 root 身份去执行此类操作,执行前也要再三检查查找起始位置是否正确。

  • A tool to synchronise Magento database between servers

    我时不时需要在测试服务器上加载生产服务器的实时数据,以前都是把数据下载到本地的测试服务器后,手工键入一些命令完成数据加载,每次都要花费几分钟时间。为了避免一再“浪费”这几分钟,我今天一次性投入了几小时完成了一个 php 脚本。虽然这是为 magento 的数据迁移而写的脚本,但我写完一看,用在其他地方也是可以的。

    为了安全起见,该脚本是用 php 命令行运行的,所有输出针对 terminal 美化,不是 browser。保存源码为 data_mover.php,同一目录下要有 mysqldump 得到的经 gzip 的 sql 文件,文件名以 FILENAME_PREFIX 开头,以 .sql.gz 结尾。启动时只需键入

    /path/to/php -f data_mover.php

    即可。

    初始化 PDO 对象时,按理只需要 host=localhost,不需要 unix_socket=MYSQL_SOCKET。但奇怪的是,如果通过 apache 调用本程序(虽然不是本程序的初衷,但我希望它在浏览器下也能运行),仅指定 host=localhost 作 PDO __construct() 参数,会产生一个莫名其妙的错误:

    SQLSTATE[HY000] [2002] Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)

    似乎是 PDO bug。可以通过 host=127.0.0.1 或者追加 unix_socket=MYSQL_SOCKET 来避免。如果通过 php 命令行启动则没有这个问题。

    而后,还涉及怎么删除所有数据表的问题。看似简单的一个问题,我发现 mysql 竟然没有一个类似于 DROP/TRUNCATE TABLE * 单行命令。于是除了本脚本用的方法外,我还想了不下两种办法:

    其一,删掉整个数据库重新创建。
    mysqladmin -f -h localhost -u (DB_ROOT_USERNAME) -p(DB_ROOT_PASSWORD) drop (DB_NAME)
    mysqladmin -h localhost -u (DB_ROOT_USERNAME) -p(DB_ROOT_PASSWORD) create (DB_NAME)
    但这需要比数据表操作更高权限的用户,在这个无关大局的脚本里去使用高权限的用户的密码,实在非我所愿。

    其二,是我 google 来的,方法很巧,但很可惜,因为 foreign keys 的存在,运行这条命令会出错。
    mysqldump -u (DB_USERNAME) -p(DB_PASSWORD) –add-drop-table –no-data (DB_NAME) | grep ^DROP | mysql -u (DB_USERNAME) -p(DB_PASSWORD) (DB_NAME)

    
    <?php
    
    define('MYSQL_SOCKET', '/path/to/mysql/socket');
    define('DB_NAME', 'db_name');
    define('DB_USERNAME', 'db_username');
    define('DB_PASSWORD', 'db_password');
    define('FILENAME_PREFIX', 'filename_prefix');
    define('TEST_URL', 'http://test.domain/');
    
    if ($handle = opendir(dirname(__FILE__))) {
    $found = false;
    /* This is the correct way to loop over the directory. */
    while (false !== ($file = readdir($handle))) {
    if (substr($file, 0, 12) == FILENAME_PREFIX && substr($file, -7) == '.sql.gz') {
    if ($found) {
    //compare which one is newer
    if (filemtime($file) > filemtime($fileFound)) {
    $fileFound = $file;
    }
    }
    else {
    $found = true;
    $fileFound = $file;
    }
    }
    }
    
    if ($found) {
    echo "Found the newest file $fileFound, and will work on it.\n";
    try {
    $pdo = new PDO(
    'mysql:host=localhost;dbname=' . DB_NAME . ';unix_socket=' . MYSQL_SOCKET,
    DB_USERNAME,
    DB_PASSWORD,
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
    );
    echo "Connected to database.\n";
    }
    catch (Exception $e) {
    die ("Error occurred when connecting to database. Quoting error message: " . $e->getMessage() . "\n");
    }
    $sql = 'SET FOREIGN_KEY_CHECKS = 0';
    $pdo->prepare($sql)->execute();
    
    /* query all tables */
    $sql = "SHOW TABLES FROM ". DB_NAME;
    $stmt = $pdo->prepare($sql);
    $stmt->execute();
    $result = $stmtDale->fetchAll(PDO::FETCH_COLUMN, 0);
    $magentoTableNames = array();
    /* add table name to array */
    foreach ($result as $tableName) {
    if (substr($tableName, 0, 8) == 'magento_') {
    $magentoTableNames[] = $tableName;
    }
    }
    
    //drop all magento tables
    $count = count($magentoTableNames);
    if ($count > 0) {
    $sql = 'DROP TABLE '. implode(',', $magentoTableNames);
    $pdo->prepare($sql)->execute();
    echo "Found and dropped $count magento tables.\n";
    }
    else {
    echo "No existing magento tables found.\n";
    }
    
    //import data via pipe
    echo "Importing data. It may take a while...\n";
    $output = shell_exec("gunzip < $fileFound | mysql -h localhost -u " . DB_USERNAME . " -p" . DB_PASSWORD . " " . DB_NAME);
    echo "Importing data completed.\n";
    if ($output) {
    echo "This is output during data import:\n$output\n";
    }
    
    //after import, change some data for test domains
    $sql = 'UPDATE magento_core_config_data SET value=? WHERE scope=? AND scope_id=? AND path=?';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array(TEST_URL, 'default', 0, 'web/unsecure/base_url'));
    $stmt->execute(array(TEST_URL, 'default', 0, 'web/secure/base_url'));
    $stmt->execute(array(TEST_URL, 'websites', 2, 'web/unsecure/base_url'));
    $stmt->execute(array(TEST_URL, 'websites', 2, 'web/secure/base_url'));
    echo "Config data changed to fit testing environment.\n";
    echo "All missions completed.\n";
    }
    else {
    echo "File not found. What else can I do?\n";
    }
    closedir($handle);
    }
    else {
    echo "File access not permitted.\n";
    }