Posts tagged PHP

doctrineLogo

PHP – Doctrine – Soft Delete – Multiple Rows at a Time

1

One of the big fallacies of working with an ORM is that behind the scenes there may be high number of queries being executed to support the application. For example, say you want to delete several messages from a user’s inbox. We create a simple form; message subject, time message was sent, status (read, unread) and a checkbox. The checkbox is used for selecting specific messages upon which we wish to conduct a specific action, let’s say delete for example. If I select ten messages at a time, for delete, and follow the normal ORM process, I would have to load each message object, then call the delete() method for each one. If you tail your query log you will notice that you just issued twenty queries; ten queries to load the message, then ten messages to delete the object. Why all this overhead when we can use MySQL’s ‘IN’ clause and issue only one delete query?

At first I added a ‘status_id’ column for each message, which would indicate if the message was in a ‘deleted’ state, otherwise known as a “soft delete”. But then I remembered that Doctrine offers this behavior via the schema config file using the ActAs: SoftDelete construct. When done reading I learned that I needed to add a ‘deleted_at’ column to my user_message table to support Doctrine’s soft delete behavior. After some thought I agreed that this would be a better approach, as opposed to using a ‘status_id’ column. Mainly because there is a precedent regarding datetime columns and Doctrine behaviors, for example created_at and updated_at; timestampable behavior.

Unfortunately, in order to take advantage of the SoftDelete behavior, I would have to issue the aforementioned twenty queries, but this time when I call the delete method on each object, it would only update the deleted_at column, vs doing a hard delete (removing it from the table permanently). So how could I take advantage of the SoftDelete behavior AND using only one query, after some tinkering I came up with the following DQL statement:

public function unlink(array $messageIds)
{
    $q = Doctrine_Query::create()
        ->update('UserMessage')
        ->set('deleted_at',  new Doctrine_Expression('NOW()'))
        ->whereIn('id', $messageIds)
        ->limit(count($messageIds));
        
    $q->execute();
}

Notice the name of the method, I prefer using link/unlink naming convention, to indicate that I may be deleting one or many rows, and the method is strategically placed within the correct class, in case you have a pivot table breaking up a M:M relationship, or on a source table with no regard to any ancillary tables.

With the above example, you now only need issue one query to affect multiple rows, and still use the MySQL server’s timezone, vs using PHP date functions which rely on the local systems timezone.

doctrineLogo

PHP – Doctrine – DQL – Select Subquery

0

Whilst working on a recent project I needed the ability to do a subquery look-up as part of a select statement. Basically I needed to count the number of rows, without having to do a group by on the entire result set. After a few minutes of research I noticed there was no real definitive source on how to do do a subquery as a select using DQL, although there were some references to using a subquery within a where clause.

Below is a code snippet which worked for me:

$q = Doctrine_Query::create()
    ->select('t1.*')
    ->addSelect('t2.*');

// You can put this anywhere within the select clauses, but must be done before  you make call to from()        
$subQ = $q->createSubquery()
    ->select('COUNT(*)')
    ->from('Table3 t3')
    ->innerJoin('t3.Table4 t4')
    ->innerJoin('t4.Table5 t5')            
    ->where('t4.status_id = :statusId')
    ->andWhere('t5.id = t2.code_id');
        
$q->addSelect(sprintf('(%s) AS my_count', $subQ->getDql()))
    ->from('Table1 t1')
    ->innerJoin('t1.Table2 t2')
    ->where('t2.user_id = :userId', array('statusId' => $statusId, 'userId' => $userId);

// And you should now be able to access the count value using:
$result = $q->execute();

var_dump($result->my_count);

// Depending on how you have your models setup, you may have to do this instead:
var_dump($result->getFirst()->getMyCount();

Note, the table names are abstract for obvious reasons, but the snippet should demonstrate the ability to add a select subquery to a dql statement.

Here’s another example usage:

$q = Doctrine_Query::create()
    ->select('uh1.*');
        
$subQ = $q->createSubquery()
    ->select('uh2.user_id')
    ->from('UserHash uh2')
    ->where('uh2.hash = :hash');
        
$q->addSelect(sprintf('(%s) AS derived_user_id', $subQ->getDql()))
    ->from('UserHash uh1')            
    ->where('uh1.type_id = :typeId')
    ->having('uh1.user_id = derived_user_id', array('hash' => $hash, 'typeId' => $typeId));
        
return $q->execute();
symfony_logo

Symfony – Share Template Across Multiple Apps

0

I am currently working on a new project where I wanted multiple apps (frontend (fe) and backend (be)) to have the same exact appearance, and without having to duplicate the code in both template/layout.php files. So what I did is chose the fe layout.php file as the master template, then made the following changes:

// config/ProjectConfiguration.class.php
public function setup()
{
    ....

    sfConfig::set('masterTemplateUri', sprintf('%s/apps/fe/templates/layout.php', sfConfig::get('sf_root_dir'));

    ....
}

// apps/be/templates/layout.php
<?php include sfConfig::get('masterTemplateUri') ?>

Now I can share the same template across both fe and be apps and only have to make code changes in one location.

phpLogo

PHP – ZipArchive – 5.3.x – Weird Issue when Unlinking a File Just Added to Archive

0

At work, I was tasked with porting some cronjobs from an old server (php 5.2.x) to a new server (php 5.3.x) and ran into a weird issue. The code to be ported was explicitly unlinking a file which was just added to the ZipArchive, in efforts to keep the filesystem clean, and ran fine on php 5.2.x, but when I ported the same code over to php 5.3.x I couldn’t get the zip file to write in it’s entirety.

Below is the code snippet:

$zip = new ZipArchive;

$zip->open('/path/to/file.zip', ZipArchive::OVERWRITE);

foreach ($files as $file) {

    $zip->addFile((string $file));

    // This is the offending line
    unlink((string) $file);
}

After I commented out the unlink, everything worked as expected. Thought I would bring it up in case anyone else faces the same situation.

symfony_logo

PHP – Symfony – Speed up CLI Commands – Do Not Load Web Plugins

0

Below is a code snippet which I use to ensure that only necessary plugins are loaded when running symfony commands via Command Line Interface (CLI).

// config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        ....

        //-----
        // Plugins
        //-----
        $basePlugins = array(
            'sfDoctrinePlugin',
            'userPlugin'
        );

        // Only add web plugins if we are not being accessed via cli
        if (php_sapi_name() === 'cli') {
            $webPlugins = array();
        } else {
            $webPlugins = array(
                'sfFormExtraPlugin',
                'sfJqueryReloadedPlugin',
                'icsicsBreadcrumbsPlugin'
            );
        }

        $this->enablePlugins(
            array_merge($basePlugins, $webPlugins)
        );
        //------

        ....
    }
}

Code is trivial, basically any plugins you need to add for CLI and web should be added to $basePlugins array, any web only plugins should be added to $webPlugins array as long as PHP is not running in CLI mode.

symfony_logo

PHP – Symfony – Build Model – No yml schema found

0

Working on a new project which requires more complex user account functionality than the current sfDoctrineGuard provides and stumbled upon this error message when attempting to build the model: “No yml schema found in /tmp/doctrine_schema_81014.yml”. There were two issues I had, which after resolving, allowed me to build my model as expected.

First, I added some custom code to my ProjectConfiguration.class.php which will only load certain plugins based upon whether I was running symfony from the Command Line Interface (CLI) or not. For example, when running commands from CLI, I don’t want to load sfFormExtraPlugin or sfJqueryReloadedPlugin, so I had to make sure that my custom plugin was added to the array. You can view the code snippet here (demonstrative only, has no impact on problem outlined in article).

Second, I had the schema.yml located @ plugins/userPlugin/config/doctrine/schema.yml, a uri which Symfony is unaware of. To make symfony aware of the custom location for my config files, I had to add the following code to plugins/userPlugin/config/app.yml:

# plugins/userPlugin/config/app.yml
all:
  userPlugin:
    config_dir: %SF_PLUGINS_DIR%/userPlugin/config
    recursive: true

The important settings is the `recursive: true` setting. After resolving both issues I cleared cache and was able to successfully build the model.

phpunit_logov2

PHPUnit – You must not expect the generic exception class

4

While working on updating my unit tests from PHPUnit v3.2 to v3.6, I came across the error message; “You must not expect the generic exception class”, upon first glance it sort of made sense, but after some thought and investigation, it really doesn’t make much sense. My preference is to throw exceptions, and codes, versus throwing specific exceptions, and the code which threw the error was doing just that, throwing a generic exception with an exception code. I spoke with the PHPUnit developer via github, and he stated that a fix is in 3.7 but will not added in 3.6 (current stable). Well I can’t wait for 3.7 to be released so I came up with the following solution:


/**
 * @expectedException Test_Exception
 */
 public function testDivByZero()
 {
     try {
         // Fyi you don't need to do an assert test here, as we are only testing the exception, so just make the call
         $result = $this->object->div(1,0);
     } catch (Exception $e) {
         if ('Exception' === get_class($e)) {
             throw new Test_Exception($e->getMessage(), $e->getCode());
         }
     }
 }

// Test_Exception.php
class Test_Exception extends Exception
{
    public function __construct($message = null, $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}

Basically all I did was create my own exception class, then when testing if an api call throws an exception, I would check the class of the exception, and if it was a ‘generic’ (Exception) class, then I would wrap it within the custom, expected Test_Exception.

phpLogo

PHP – Correctly Cast Numeric Strings to Numeric Datatypes

0

Recently I posted an article on how to compare two doctrine objects @ http://melikedev.com/2012/06/13/doctrine-php-compare-two-doctrine_record-objects/ which works well in most cases, but ran into an issue yesterday where floats as strings were being set to 0 due to the (int) cast. This was a problem because on the http://melikedrinks.com website, I store ingredient portions in a float (decimal) format, which means that my object comparisons were not being evaluated correctly if I changed the ingredient portions from .5 to .66 as they would both be cast to int with a value of 0.

So I was working on a possible solution and came across a great little hack in the comments section of the docs for the is_numeric function @ http://us2.php.net/is_numeric, in the comments, a user (dave.marr@gmail.com), pointed out that you can add ‘+0′ to a string which evaluated to true using the is_numeric function and it will correctly cast the value of the string to the proper datatype, which worked well in converting string floats to float floats. So the revised code would look like:

$objectArray1 = array_map(function($value) { return (is_numeric($value)) ? ($value + 0): $value; }, $object1->toArray(false));
phpLogo

PHP – Reflection Class – Determine Parent Method Signature

0

As I was writing unit tests against a new caching wrapper which extends the Memcached API, I ran across an issue when overriding the Memcached::get() method. According to PHP docs regarding the Memcached::get() method, there are three arguments which which must be added to my extended signature, which I added, but kept getting “… should be compatible with that of Memcached::get()” errors. I tried looking for the method signature via source code but didn’t glean anything useful from the PECL documentation, so I turned to PHP’s RelectionClass to see if I could figure out what I was missing from my extending class signature which was causing the aforementioned errors. After a few minutes I ended up with the following code snippet:


$this->cache = Cache::factory(Cache::TYPE_VOLATILE);

$reflector = new ReflectionClass(get_class($this->cache));

foreach ($reflector->getMethod('get')->getParameters() as $param) {
    var_dump((string) $param);
}

Which outputted the following:


string(32) "Parameter #0 [ <required> $key ]"
string(37) "Parameter #1 [ <optional> $cache_cb ]"
string(39) "Parameter #2 [ <optional> &$cas_token ]"

After a few seconds investigating the output, I noticed that I wasn’t passing the third argument ($cas_token) by reference, but before I fixed my version I double checked the PHP docs regarding Memcached::get() and in fact noticed they indicated that $cas_token was indeed being passed by reference (as indicated by the ampersand &). After I altered my extended method to pass the third argument by reference, everything worked as expected. So if you ever need to introspect an API with little to no relevant documentation, try using PHP’s ReflectorClass to get the information you are after.

Linux Logo

CentOS – PHP – Install Memcached (with a d) Stack

0

Recently I started retro-fitting the MeLikeDrinks.com drink website to cache frequently used data to improve performance, as such I wrote a light, custom cache API which sits on top of PHP’s Memcached API. Because I follow TDD principles, I wrote out the tests first, which helped me write out the API calls needed to support the application, but when it came time to actually get the memcached service on my CentOS box, I ran into all sorts of confusion, which motivated me to write this article. It is my hope that this article will help alleviate any confusion others may face when they decide to dive into the cache pool.

First, I need to clarify one of the more confusing issues regarding PHP and Memcache, which is, there are two different PHP Apis. Depending on which PHP Memcache API you select, will determine the steps necessary for PHP to gain access to the underlying memcached server. The differences between PHP Memcache and PHP Memcached are outlined here. Regardless of the differences between the two PHP APIs we have to choose from, they both access the same underlying Memcached Service. The only REAL difference, as far as configuration and installation steps are concerned, is that PHP Memcached requires an external library known as libmemcached. In short, the stacks look like this:

PHP (PECL) Memcache

PHP (PECL) Memcached

  •  libevent (dependency)
  • memcached (service)
  • libmemcached
  • zlib (PHP dependency)
  • PHP (PECL) Memcached

As you can see, the stacks are nearly identical, except for the fact that PHP Memcached requires an extra layer; libmemcached. If you opt to use PHP Memcache, and because this article assumes you are using CentOS,  you can simply have YUM install the entire stack for your via `yum install php-memcache`. If your environment requires you to compile PHP, then you can issue a `yum install memcached` command, and YUM will install libevent and memcached, then you can compile PHP (and PHP Memcache module).

If you are still reading, then you want to install and use PHP Memcached, which unfortunately will require a little more work on your end. I will not go over how to install PHP Memcached using PECL, as I do not believe in these types of automated processes. In the past I have had bad experiences with PECL and rather not introduce another layer of complexity, so the following steps will allow you to compile and install the PHP Memcached stack without PECL.

Steps required:

  • libevent (dependency)
    • yum install libevent libevent-devel
  • memcached (service)
    • yum install memcached
  • libmemcached
    • First check which version of PHP Memcached you wish to use, which will determine which version of libmemcached you need. For example; according to PHP PECL Memcached changelog, the latest version of libmemcached you can use is 1.0.4, otherwise if you try to use a newer version of PHP PECL Memcached you may run into unforeseen issues, in other words, you should ALWAYS assume that PHP PECL Memcached is a few versions behind libmemcached.
    • Based on which version of libmemcached you need from the previous step, you can download from libmemcached download page.
    • Extract file and CD into dir
    • $ -> ./configure –with-libevent-prefix=/usr
    • $ -> make
    • $ -> make install
  • PHP PECL Memcached
    • Download the correct version based on which version of libmemcached you compiled and installed via changelog (which links to download) page.
    • Extract file and CD into dir
    • $ -> phpize
    • $ -> ./configure –with-libmemcached-dir=/path/to/where/memcached.h/is/located
    • make
    • make install
  • PHP ini config
    • vi /path/to/php.ini
    • Add: extension=memcached.so
  • Test module installation
    • $ -> php -m
      • You should memcached listed among other modules
    • $ -> php -i | fgrep -irs cache
      • You should see various memcached config settings
  • Finishing touches
    • Restart apache
    • Start memcached
    • Write a test script to test the setting and getting of a value from your cache server via PHP Memcached API.

And there you have it, a memcached stack without using PECL. All things considered it should not have been too painful an installation, however I must make one disclaimer; I customized my memcached stack a bit more than I eluded to in this article, so if you run into an issue, just post a comment and I will try to help you resolve the issue.

Now that you have a memcached service, and an API to use, you should start focusing on the code points of your application with the most overhead, this will give you the most bang for your buck when you start caching data. Good luck, and happy caching.

Go to Top