Category Archives: tricks

Crop image before upload

Let’s talk about cropping and resizing today. This quite common task on any web app. And as a rule there are 2 issues with image size.

original image is too big and it doesn’t make sense to return original to client. We need to make it smaller.
image size is not proportional i.e. width is much bigger than height or vice versa.
too_wide

Dynamically resize images.

Let’s think how we can solve issue #1. The easiest solution is resize image on upload. And then return to client resized images. This will works fine until you need to get same image with bigger or smaller size. We can use browser resizing but you know it sucks. This lead us to decision that allow us to return image with different sizes on fly.

For example we can use such URLs:
/uploads/users/1.png
/uploads/cache/users/1_200_x_200.png
/uploads/cache/users/1_500_x_500.png

You got the idea, right? We will check do we have image with required size in cache if yes- just return it, otherwise create and return. We keep the originals and can easily clean cache folder.

With php we can use https://github.com/avalanche123/Imagine nice library that allows you to make various actions with your images.

Cropping images.

We can use suggested logic but still we have an issue with proportions. For example I’m uploading my another selfie. And I’m not the best photographer. What do we get using logic described below.

bad_cropping

Resizing libraries don’t know how to crop your image and will use center and provided proportions to crop your image. As a result you’ll get image like this. But where is my mouth? 🙂

In such examples when user uploads his avatar or image is quite important and you need to get best of it you can combine first approach with manual cropping.

There’s a dozen of cropping plugins http://www.jqueryrain.com/demo/jquery-crop-image-plugin/ but they work in pretty similar way. They allow use to crop image and then will provide width, height and x,y  points.  Personally I used this one http://fengyuanchen.github.io/cropper/

Finally I provide a piece of javascript code that I used. Ok so you click “Update avatar” we ask user to provide image. After that show popup with cropping dialog. Ask user to crop image and send received data – image, and cropping params to server (x, y, width, height). On server crop the image – save original and then using original cropped image return copies of different sizes.

<!-- our trigger -->
<div class="fileinput-wrapper">
    <button class="block-btn btn text-uppercase btn-primary browse-files fileinput-button">
        Change Icon
        <input type="hidden" name="Profile[avatar_url]" value="">
        <input type="file" id="profile-avatar_url" class="change_profile_avatar" name="Profile[avatar_url]" data-id="70">
        <input type="hidden" id="profile-crop_params" name="Profile[crop_params]" value="">
    </button>
</div>

<!-- cropper dialog -->
<div class="modal fade" style="" id="crop-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                    <span aria-hidden="true">&times;</span>
                    <span class="sr-only">Close</span>
                </button>
                <h4 class="modal-title" id="myModalLabel">Crop Image</h4>
            </div>
            <div class="modal-body">
                <div class="eg-main">
                    <div class="eg-wrapper" id="cropper_original_wrapper">

                    </div>
                    <div class="eg-preview clearfix">
                        <h3>Large Preview</h3>
                        <div class="preview preview-md"></div>
                    </div>
                    <div class="eg-preview clearfix">
                        <h3>Small Preview</h3>
                        <div class="preview preview-xs"></div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary" id="save_profile_avatar">CROP & FINISH</button>
                <button type="button" class="btn btn-inverse" data-dismiss="modal">Cancel</button>
            </div>
        </div>
    </div>
</div>
 function initAvatarUpdate() {
        $('.change_profile_avatar').fileupload({
            dataType: 'json',
            add: function (e, data) {
                var $element = $(this);
                data.url = "/profile/update?id=" + $element.data("id");
                $("#cropper_original_wrapper").empty();
                if (data.files && data.files[0]) {
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        var newImg = $("<img>", {"class": "cropper", "src": e.target.result});
                        $("#cropper_original_wrapper").append(newImg);
                        initCropper($element);
                    }
                    reader.readAsDataURL(data.files[0]);
                    $("#crop-modal").modal("show");

                    $("#save_profile_avatar").off('click').on('click', function () {
                        data.submit();
                        return false;
                    });
                }
            },
            done: function (e, data) {
                var newSrc = data._response.result.model.fileUrl;
                $(this).closest('.profile-avatar').find("img").attr("src", newSrc);
                $("#crop-modal").modal("hide");
                $("#profile_avatar").trigger("profile_changed", [ $(this).data("id"), newSrc])
            }
        });

        function initCropper($element) {
            var $cropper = $(".cropper")
            $cropper.cropper({
                aspectRatio: 1,
                data:
                {
                    x: 1,
                    y: 1,
                    width: 500,
                    height: 500
                },
                preview: ".preview",
                // autoCrop: false,
                // dragCrop: false,
                // modal: false,
                // moveable: false,
                // resizeable: false,
                // maxWidth: 480,
                // maxHeight: 270,
                // minWidth: 160,
                // minHeight: 90,
                done: function(data) {
                    $element.next().val(JSON.stringify({
                        "x": data.x,
                        "y": data.y,
                        "width": data.width,
                        "height":data.height
                    }));
                }
            });
        }
    }

Finally we got

good_cropping

Bootstrap form helpers get instances

Worked with bootstrap form helpers library. Looks nice and great integration with twitter bootstrap. But spent a lot of time to figure out how to access bfhdatepicker and bfhtimepicker objects. Unfortunately googling didn’t get any answer. As usual key was simple 🙂 Hope this will save time for someone else.

   var datePicker = $('#datepicker_id').data('bfhdatepicker');
   datePicker.setDate('today')

   var timePicker = $('#timepicker_id').data('bfhtimepicker');
   timePicker.setTime('now')

Postgres doesn’t create indexes for foreign keys

Story about another #epicfail. I understood if everything is ok I don’t like to write about that cause it looks simple. But when something goes wrong 🙂

Well I worked with Postgres last time about 4 years ago. So definitely I wasn’t a good postgres guys. Last 4 years in most cases I worked with MySql. And mysql automatically creates indexes for foreign keys. Some kind of “second system effect”. When you even don’t think about indexes cause you’re sure that they already should be created.

Well my topic would be useless if I didn’t provider any elegant solution. Right ?

So feel the power of postgres.


SELECT
	tc. TABLE_NAME,
	kcu. COLUMN_NAME
FROM
	information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu ON tc. CONSTRAINT_NAME = kcu. CONSTRAINT_NAME
JOIN information_schema.constraint_column_usage AS ccu ON ccu. CONSTRAINT_NAME = tc. CONSTRAINT_NAME
WHERE
	constraint_type = 'FOREIGN KEY'
ORDER BY table_name

This will return table and column names for ALL foreign keys. Then you can just take this result to generate indexes.

Do not repeat my errors.

Yii migration use change in addition to up and down

Finally I pushed some of my yii extensions to github.
https://github.com/radzserg/yii-Rz

Still need to work on README.md I have lack of time now. All in all I think I can refer to it now.

And as a bonus I will tell about my extension for CDbMigration. A lot of migration tools/lib already support change() method in addition to up() and down(). 

But the main problem here – it’s easy to implement it if we create table or add a column or index in this case we know that we need to do in down action. It’s easy we need to remove appropriate object. For example:

    public function up()
    {
        $this->addColumn('user', 'email', 'varchar(255) AFTER username');
        $this->addColumn('user', 'password', 'varchar(255) AFTER email');
    }

    public function down()
    {
        $this->dropColumn('user', 'email');
        $this->dropColumn('user', 'password');
    }

Easy enough. But what do we need to do in down action if we create table. In this case everything is quite complicated. We either need to keep DB snapshot for each migration. Or create it going through migration until some migration that you need. Both quite difficult to implement and I guess even if you can implement such approach you still have a lot of problem.

The good thing here that in most migrations we add or create something. And we don’t need to care about down function. And in that cases when we need to implement down functionality we will do that using closures.

Here are my code
https://github.com/radzserg/yii-Rz/blob/master/db/ChangeMigration.php

Here are examples of some of my migrations

<?php

class m130626_132012_add_not_confirmed_users extends Rz\db\ChangeMigration
{
    public function change()
    {
        $this->addColumn('sm_user', 'confirmed', 'TINYINT(1) NOT NULL DEFAULT 0 after create_time');
        $this->addColumn('user', 'confirmed', 'TINYINT(1) NOT NULL DEFAULT 0 after create_time');

        $this->alterColumn('sm_user', 'user_id', 'INT', function() {
           $this->alterColumn('sm_user', 'user_id', 'INT NOT NULL');
        });
    }

}

When you run yiic migrate down. Migration will save queue of back actions in this example

  • remove column sm_user.confirmed (autogenerated)
  • remove column user.confirmed (autogenerated)
  • alter sm_user change user_id to int not null (our manual down closure)

And finally execute all this action in reverse order that’s it. No need to care about down functionality and order.

I proposed that approach to yii core team https://github.com/yiisoft/yii2/issues/593#issuecomment-20397820 but they refuse it. Well maybe it’s correct when we talk about framework. Anyway I decided to implement and try it. And for now I like it 🙂

Try it too and let me know do you like it or no.

How do I send emails in my applications

Today I decided to share some of my ideas according to email sending. I will use yii app as an example but it’s absolutely not required. It’s just an aproach that you can use with any other framework. Right now yii doesn’t have default mail component. But I won’t tell you what mail extension or lib should you use. I want to tell you about how should look like email send in your app.

Well I really believe that you already don’t send emails like this 🙂

$message = " Pellentesque convallis tempor tortor. Nullam nec purus.";
mail('somebody@example.com', 'Nonsensical Latin', $message);

But you do it like this

     $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('send@example.com')
        ->setTo('recipient@example.com')
        ->setBody(
            $this->renderView(
                'HelloBundle:Hello:email.txt',
                array('name' => $name)
            )
        )
    ;
    $this->get('mailer')->send($message);

It could be Swift or Zend Mailer or other good lib.

What are advantages in this approach

  • First of all you don’t create a wheel but use good and checked tools
  • It doesn’t render email body inside your code. It renders it from some view. Very good, take five

But what are disadvantages in this approach

  • it’s not DRY. In real app you will send dozens of emails. And following this approach you’ll have dozens places with the same piece code. 
  • it’s doesn’t scalable at all if tomorrow you want to log sent emails or want to render it in html, or add bcc etc, you have to to add additional logic in dozen of places. 
  • it’s quite difficult to update email template. If we have some managers, moderators, admin that sometimes want to improve some text in email they can’t do that without asking developer.

So what is my proposal?

Email send have to look like this

$emailReset = new \MyApp\email\ResetPassword(array(
    'user' => $userModel,
    'hash' => $hash,
    /* other dynamic data for email */
));
$emailReset->send();

And that’s it! You don’t need another code. You create an obejct of required email, in my example it’s reset password email. I pass user model, so then we can get email, username, etc and I pass specific data for that email. And… I send it.

How we can achieve that?
We have various emails in our app – sign in, reset password, sale emails, promo emails, and many many others. For each email we know subject and body.  The idea is to keep email templates in DB

email

Nothing fancy at that point. Then I add parent class for all my emails

<?php

namespace MyApp\email;

abstract class Base
{

    /**
     *
     * @var TransportInterface
     */
    private static $_transport;

    /**
     *
     * @var \MyApp\model\EmailTemplate
     */
    private $_emailTemplate;
    protected $_emailCode;
    private $_addToEmail;
    private $_addToUsername;
    private $_data;

    /**
     * Recipient
     * @var \MyApp\model\User
     */
    private $_user;

    public function __construct($data)
    {
        $data = $this->_initData($data);

        if (!empty($data['user']) && ($data['user'] instanceof \MyApp\model\User)) {
            $this->_user = $data['user'];
            $data['email'] = $this->_user->email;
            $data['username'] = $this->_user->getUsername();
        } else {
            if (empty($data['email'])) {
                throw new \Exception("Email is not defined");
            }
        }
        $this->_addToEmail = $data['email'];
        if (!empty($data['username'])) {
            $this->_addToUsername = $data['username'];
        }
        $this->_data = $data;
    }

    protected function _initData($data)
    {
        return $data;
    }

    /**
     * Compile and send email
     */
    public function send()
    {
        $email = $this->_emailTemplate();

        $email->compileEmail($this->_data);

        $defaultEmailParams = \Yii::app()->params['email'];
        $mail = new \Zend\Mail\Message();
        $mail->setBody($email->body);
        $mail->setSubject($email->subject);
        $mail->addTo($this->_addToEmail, $this->_addToUsername);
        $mail->setFrom($defaultEmailParams['defaultFromEmail'], $defaultEmailParams['defaultFromName']
        );

        //$mail->addHeader('List-Unsubscribe', '<' . 'link' . '>');
        $this->getTransport()->send($mail);

        return true;
    }

    /**
     * Return email transport
     * @return \Zend\Mail\Transport\TransportInterface
     */
    public static function getTransport()
    {
        if (!self::$_transport) {
            if (\Yii::app()->params['email']['provider'] == 'sendmail') {
                self::$_transport = new \Zend\Mail\Transport\Sendmail();
            } elseif (\Yii::app()->params['email']['provider'] == 'file') {
                $fileOptions = new \Zend\Mail\Transport\FileOptions();
                $fileOptions->setPath(\Yii::app()->basePath . '/runtime/cache/email/' . APPLICATION_ENV);
                self::$_transport = new \Zend\Mail\Transport\File($fileOptions);
            }
        }
        return self::$_transport;
    }

    /**
     * Return email object
     * @return \MyApp\model\EmailTemplate
     * @throws Exception
     */
    protected function _emailTemplate()
    {
        if (!$this->_emailCode) {
            throw new Exception("Template name is not set");
        }
        if (!$this->_emailTemplate) {
            $this->_emailTemplate = \MyApp\model\EmailTemplate::getByCode($this->_emailCode);
        }

        return $this->_emailTemplate;
    }

}

It’s just an example. So your class be different could more functionality that I described above. I show just an approach. I used ZendMail component it’s so easy now if you user composer (  “zendframework/zend-mail”: “*” ) but you wanna use what ever you want. I believe everything is quite straight forward here.

Only the one note about $email->compileEmail($this->_data) I liked to use Twig to render emails. So compileEmail in fact just render twig template that we have in db and  copy in file. But it’s another topic. If anyone would be interested I can describe that in more detail.

Finally the example of specific email is following

<?php

namespace MyApp\email;

class ResetPassword extends \MyApp\email\Base
{

    protected $_emailCode = 'reset_password';

    protected function _initData($data)
    {
        $data['link'] = \Yii::app()->createAbsoluteUrl('urltoreset', array(
            'email' => $data['user']['email'],
            'hash' => $data['hash']
        ));

        return $data;
    }

}

That’s it. Hope it would be helpful for someone 🙂

I forgot to remove debug code and updated last changes to production

Sometimes you need to debug some code and you do something like this

if ($service->canBeUpdated()) {
    // $service->canBeUpdated returns false 
    // and you need to debug this place 
}

// and you do simple (very simple) stub
if (true || $service->canBeUpdated()) {
}

😀 ooh everything works and you can easily debug this place. You’ve finished with that task and did some other tasks and finally updated everything to live.

Oh crap.. I forgot to remove debug code and updated last changes to production (or some customer complains that smth is wrong and this is even worst)

So I use this simple component in order to prevent such situations. Because to be honest I faced with such problems not one time.

<?php
 
/**
 *
 * User: radzserg
 * Date: 3/6/12
 */
  
class App_Debug
{
 
    /**
     * @static
     * @param $val
     * @return bool
     */
    public static function alwaysTrueForDebug($val)
    {
        if (APPLICATION_ENV == 'development') {
            return true;
        } else {
            return $val;
        }
    }
 
    public static function setVarForDebug(&$var, $value)
    {
        if (APPLICATION_ENV == 'development') {
            $var = $value;
        }
    }
 
}
// Now I can do it in this way 
if (App_Debug::alwaysTrueForDebug($service->canBeUpdated)) {
// or
App_Debug::setVarForDebug($user['state'], 'active')
 
// first expression will be true and $user['state'] will be set 
// if only APPLICATION_ENV == 'development'

Exteneded Yii CConsoleCommand

Continue to write about yii and sharing my experience. Today I write about how do I exteneded CConsoleCommand.First of all I wanna say that base class CConsoleCommand is all what you need to run console commands. And I extended it for my purposes. But everybody can want to do it in another way or add another functionality. So all of this is just an example of extending base class.

I’ve added few additional abilities

  • pretty verbose method (add ./yiic myCommand –verbose)
  • ability to run only one copy of script
  • if verbose is set, show script execution time
<?php
 
/**
 * Extended yii console command
 *
 * @author: radzserg
 * @date: 11.04.11
 */
class ConsoleCommand extends CConsoleCommand
{
 
    const VERBOSE_ERROR = 'error';
    const VERBOSE_INFO = 'info';
    const VERBOSE_SYSTEM = 'system';
 
    public $verbose;
 
    private $_lockFile;
 
    // if false this means that multiple scripts can work simultaneously
 
    protected $_isSingletonScript = false;
 
    // calculate time execution time
 
    protected $_timeStart;
 
    protected function _verbose($message, $level=null, $type=null)
    {
        if (!$this->verbose) {
            return ;
        }
 
        $level = (int)$level;
        $indent = str_repeat("\t", $level);
        if ($type == self::VERBOSE_ERROR) {
            // message in red
            $message = "\033[31;1m" . $message . "\033[0m\n";
        } elseif ($type == self::VERBOSE_INFO) {
            // message in green
            $message = "\033[32;1m" . $message . "\033[0m\n";
        } elseif ($type == self::VERBOSE_SYSTEM) {
            $message = "\033[33;1m" . $message . "\033[0m\n";
        }
 
        echo $indent . date('H:i:s ') . $message . "\n";
    }
 
    protected function beforeAction($action,$params)
    {
        $this->_verbose("Start execution of " . get_class($this), null, self::VERBOSE_SYSTEM);
        $this->_timeStart = $this->_microtimeFloat();
        if ($this->_isSingletonScript) {
            $lockDir = Yii::getPathOfAlias('application.commands.lock');
            if (!is_dir($lockDir)) {
                mkdir($lockDir);
            }
            $filePath = $lockDir . '/' . get_class($this) . '.lock';
            $this->_lockFile = fopen($filePath, "w");
            if (!flock($this->_lockFile, LOCK_EX | LOCK_NB)) {
                $this->_verbose("Another instance of this script is running");
                return false;
            }
        }
        return true;
    }
 
 
 
    protected function afterAction($action,$params)
    {
        if ($this->_lockFile) {
            flock($this->_lockFile, LOCK_UN);
        }
        $time = round($this->_microtimeFloat() - $this->_timeStart, 2);
        $this->_verbose("End (time: {$time} seconds)", null, self::VERBOSE_SYSTEM);
    }
 
    private function _microtimeFloat()
    {
        list($usec, $sec) = explode(" ", microtime());
        return ((float)$usec + (float)$sec);
    }
}