Tag: php

  • 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.

  • 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

  • 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.

  • 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";
    }
    
    
  • How to override abstract class in Magento?

    If you are googling “override abstract class in magento” to find my blog, probably you already tried Mage::getModel(‘module/model_abstract’) as it worked for instantiatable classes.

    The scenario when requires override an abstract class is overriding isActive() method in Mage_Shipping_Model_Carrier_Abstract. If Mage_Shipping_Model_Carrier_Abstract can be overridden, all carrier methods’ isActive logic can be changed without changing each instantiatable class. However, whether Magento overriding mechanism can work has a prerequisite –

    All instantiation of parent class (in Mage namespace, to be overridden) are using Mage::getModel(‘module/model_class’), or $layout->getBlock(‘module/block_class’), or Mage::helper(‘module/helper_class’) instead of new Class_Name(). This guarantees Mage is the single entry point of class instantiation, then Mage Config can always instantiate an overridden class if any.

    The codes coming with Magento follow this rule to the maximum. But when it comes to class inheritance, the rule is broken. Say Mage_Class_A extends Mage_Abstract_B, Mage_Class_A makes reference to Mage_Abstract_B using normal php syntax. It means even if Mynamespace_Abstract_B is overriding Mage_Abstract_B, Mage_Class_A still inherit from Mage_Abstract_B.

    Come back to the question: How to override abstract class in Magento? Use Mage_Shipping_Model_Carrier_Abstract for example, to override isActive() method, you need to override all chid classes, e.g. Mage_Shipping_Model_Carrier_Flatrate, Mage_Shipping_Model_Carrier_Freeshipping, Mage_Shipping_Model_Carrier_Tablerate, etc.

    You may not satisfied with the answer simply because you do not want to override every carrier class. Let me raise the question again: Is there another way to override abstract class in Magento? Yes! Copy Mage_Shipping_Model_Carrier_Abstract from app/code/core/Mage/Shipping/Model/Carrier to app/code/local/Mage/Shipping/Model/Carrier, do NOT change the class name, just change or add the methods as you need. This is a trick. Magento loads a class from several locations, and app/code/local comes before app/code/core. That’s why it works.

    By the way, if php_apc is running, you must force apc to refresh for new class created in include_path. Please refer to “when does php apc refresh cache data” for details.

  • Discovery: PhProxy & CHtml

    我认为突破访问限制的终级方案是 VPN,可是设置 VPN 的技术含量有点高,到现在我还搞不掂 OpenVPN,只能徘徊于 PPTP。今天发现可爱的 php 也有了 proxy(或许早就有了,我不知道罢了),虽然我用不着,但对国内的朋友们确实是天大的福音。简单易行,一分钟就能搭一个!

    我只发现 phproxy 三个不大的缺陷:

    1. 无法代理流文件(比如无法看 youtube 上的 video,但访问 youtube 没问题)
    2. 因为 phproxy 工具栏在网页顶部,如果代理网页在相同位置绝对定位,则会重叠在一起(比如 wikipedia,我想修改一下 style.css 就好了,或者做一个自动隐藏的工具栏)
    3. 有些被代理的网页自检而跳出代理,重定向到原来的 url(比如 google spreadsheet)

    牛人真不少,还有一个 CHtml,竟然用 recaptcha 去实现突破,构思非常巧妙。

  • Comparison in PHP

    除非使用 === 或 !== 进行比较,PHP 尽可能地将值转化为数字进行比较。因此,看到比较 “10” == “1e1” 的结果是 true,千万不要大惊小怪。

    “尽可能”如何理解?

    1. 如果等式一边是数字,另一边是字符串,那么字符串一定要 cast to int 后再比较。因此,比较 “10” == 1e1 的结果是 true,比较 “10 ” == 1e1 的结果也是 true。
    2. 如果等式两边都是字符串,且字符串中均不含数学字符以外的字符(包括white space),那么两边的字符串会在 cast to int 后再比较;否则,直接比较字符串。因此,比较 “10” == “1e1” 的结果是 true,但比较 “10 ” == “1e1” 的结果是 false。
    3. 如果等式有一边或两边是数组,则先比较数组结构是否相同,再逐个比较数组元素。在比较数组元素时,索引不会 cast,但值按上述两条规则“尽可能” cast。因此,比较 1 == array(1) 的结果是 false,比较 array(“10”) == array(“1e1”) 的结果是 true,比较 array(10 => 10) == array(“1e1” => 10) 的结果是 false。
  • Do not use is_null()

    测试一个变量是否为 NULL,我曾喜欢用 is_null() 函数,现在发现还比如直接用 === 比较来得快。

    php.net 上有人写了个测试,得出的结论是 === 比 is_null() 快30倍。

    
    $v = NULL;
    
    $s = microtime(TRUE);
    for($i=0; $i<1000; $i++) {
        is_null($v);
    }
    print microtime(TRUE)-$s;
    print "<br>";
    
    $s = microtime(TRUE);
    for($i=0; $i<1000; $i++) {
        $v===NULL;
    }
    print microtime(TRUE)-$s;
    
    

    这是2007年的事情了,我不清楚他的环境配置。如今在我 Intel(R) Celeron(R) CPU 2.80GHz PC 充当的测试服务器上,running zend server php 5.2.9,得出的结论是 === 比 is_null() 快1-2倍。看来 php is_null() 的效率进步了不少,但还是慢。以后我得改用 NULL === $v 的方式了。

  • Solution to php fastcgi crashes

    One of my servers is running Fedora + Nginx + php-cgi (spawned). I noticed sometimes php-cgi crashes without a reason or warning (Nginx gives out 500 error), and I have to spawn it again.

    After some digging, I find exporting PHP_FCGI_MAX_REQUESTS to ENV cures the problem. As advised, I export PHP_FCGI_CHILDREN as well.

    To achieve that, in detail, just add the following to the top of spawn bash script.

    # Set these two env variables to reduce php-cgi crash
    PHP_FCGI_MAX_REQUESTS=1000
    export PHP_FCGI_MAX_REQUESTS
    PHP_FCGI_CHILDREN=5
    export PHP_FCGI_CHILDREN

  • Self-contained form

    Self-contained form
    Self-contained form

    视表单为一个对象