PHP. Время работы "тяжелых" скриптов

Как долго продолжают работать "тяжелые" скрипты, если не дожидаясь окончания их работы закрыть браузер? И продолжат ли они свою работу? Как показали наблюдения, они продолжают жить пытаясь выполнить поставленную перед ними работу. Многие для управления вот такого рода временем "темной" жизни скриптов прибегают к инструкции вида  

ignore_user_abort(boolean mode)

Если эта инструкция вызывается с false параметром, то она равнозначна ее отсутствию. Обычно она указывается с параметром true - ignore_user_abort (true). Это необходимо в случаях, когда нужно продолжить работу скрипта, даже если браузер сбросил соединение. Вообще в php имеются возможности для отслеживания состояния подключения. Меня заинтересовала ситуация с продолжением выполнения скрипта даже после закрытия окна браузера. Проверить это очень просто: напишите скриптик, который выполняется некоторое время, запустите его и недожидаясь окончания работы скрипта закройте окно браузера, не забудьте в скрипте добавить маркер завершения работы, например создать файлик. Так вы сможете отслеживать за выполняемостью вашего скриптика. Вообще после закрытия сеанса агента (браузера) скрипт должен умереть, но как вы можете убедиться, это не совсем так: он все же пытается прожить подольше. В этом случае есть смысл перенести его в фоновое выполнение (cron). Но все же интересно было проанализировать данную особенность. Для отслеживания состояния подключения используется инструкция connection_status(). Для дальнейших манипуляций были написаны свои обертки:

// return code of connect status
function getConnectStatus() {
    return connection_status();
}

// return type of connect status by code
function getConnectStatusByCode($connectionStatus) {
    switch ($connectionStatus) {
        case CONNECTION_NORMAL:
           $status = 'Normal';
           break;
        case CONNECTION_ABORTED:
           $status = 'User Abort';
           break;
        case CONNECTION_TIMEOUT:

           $status = 'Max Execution Time exceeded';
           break;
        case (CONNECTION_ABORTED & CONNECTION_TIMEOUT):
           $status = 'Aborted and Timed Out';
           break;
        default:
           $status = 'Unknown';
           break;
    }      
    return $status;
}

// check the connect status (true - normal, false - abnormal)
function checkConnectionStatus() {
    return connection_status() === 0;
}


Теперь можно написать функцию, которая будет имитировать какую-либо долгую работу

// imitation long script
function scriptImitation() {  
  
    $start = time();
  
    $limitIteration = 1000000;
    for ($i = 0; checkConnectionStatus() && ($i < $limitIteration); $i++) {
        // some long work...
    }
  
    $end = time();
    $runTime = $end - $start;
  
    if ($i === $limitIteration || !checkConnectionStatus()) {
        $connectStatus = getConnectStatusByCode(getConnectStatus());
        $scriptImitationLog  = "Connect status: $connectStatus";
        $scriptImitationLog .= "; ";
        $scriptImitationLog .= "Iteration: $i";
        $scriptImitationLog .= "; ";
        $scriptImitationLog .= "Run time: $runTime second";
      
        file_put_contents('scriptImitationLog.txt', $scriptImitationLog);
    }
  
}


Собственно дальше периодически запускаем с остановами работу нашего скрипта. Как бы быстро не пытался закрыть окно агента, скрипт все также успешно отрабатывал и на выходе создавался лог. Не стоит забывать про max_execution_time, дольше этого времени скрипт точно не будет жить. В нашем случае он был увеличин. Давайте заглянем в лог:

Connect status: Normal; Iteration: 1000000; Run time: 5 second

Как видно, подключение в норме, цикл отработал до конца. Так в чем же дело? В этой статье на stackoverflow нашел вот такое решение - http://stackoverflow.com/questions/2389035/php-connection-status. И связано оно было с буфером вывода: периодически в буфер вывода скрипта дописывается небольшая порция данных (например, пустой символ) с последующим сбросом (обычно для php подразумевается принудительная отправка всего содержимого с последующей очисткой). Для этого предлагается использовать совместное использование функций ob_flush и flush. И так как для сброса подразумевается наличие подключения (ведь буфер формируется для отправки в направлении браузера - инициатора), то при очередном сбросе при отсутствии нормального подключения его статус переводится в иное состояние (User aborted). Причем если в скрипте отсутствует инструкция или приведена в виде

ignore_user_abort (false);

тогда при изменении нормального статуса подключения скрипт тут же перестает выполняться, в случае наличия инструкции

ignore_user_abort (true);

даже при изменении статуса подключения скрипт продолжит выполняться до конца, но именно в этом случае можно отслеживать статус подключения. С учетом этого, немного видоизменим одну из оберток

// check the connect status (true - normal, false - abnormal)
function checkConnectionStatus() {
    print " ";
    ob_flush();
    flush();
    return connection_status() === 0;
}


Теперь все находится под контролем, и в случаях, когда мы закроем наш браузер, работа скрипта будет приостановлена или можно будет организовать иное поведение.
На последок приведу результаты еще одного наблюдения. Если сравнить время выполнения скрипта по логам до внесения изменений и после в обертку - checkConnectionStatus,
можно сделать вывод, что сброс буфера вывода занимает продолжительное время. Так время на одну итерацию в тестовом скрипте без сброса буфера в среднем заняло - 0,005 ms, со сбросом - 0,028 ms, то есть сброс буфера в среднем заняло 0,023 ms, что в 4,6 раза больше времени одной итерации. Отсюда видно, что приведенный метод отслеживания за жизнью работы "тяжелых" скриптов может увеличивать основное время работы. Данный тест проводился на Firefox version 29.0.1. Всем успехов.  

Javascript. IE or not IE

Часто при реализации того или иного скрипта необходимо учитывать вопросы кроссбраузерности, в частности - определение типа браузера. Анализ значения свойства navigator.userAgent в этом случае считается не совсем безопасным (может быть подменен пользователем), более правильнее - определение типа по наличию поддержки так называемых  условных комментариев.
Инструкция:

var isIE = /*@cc_on!@*/false;

установит переменную isIE в значение false во всех браузерах (так как они игнорируют комментарии), но только не в IE из-за оператора отрицания ! в условном комментарии. Данная инструкция воспринимается IE следующим образом:

var isIE = !false; // true

javascript. About objects

При вызове с оператором new функция-конструктор всегда возвращает объект. По умолчанию это объект, на который указывает ссылка this. Если внутри конструктора к нему не добавляются никакие свойства, возвращается "пустой" объект ("пустой", если не считать свойства и методы, унаследованные от прототипа конструктора). Конструкторы неявно возвращают значение this, даже если в них отсутствует инструкция return. Однако сохраняется возможность вернуть любой другой объект по своему усмотрению. Стоит помнить, что если вызвать конструктор без оператора new, ссылка this будет указывать на глобальный объект. В браузерах ссылка this будет указывать на объект window. Если в конструкторе создается какое-либо новое свойство, такое как this.member, то при вызове конструктора без оператора new будет создано новое свойство глобального объекта, к которому можно будет обратиться так: window.member или просто member. Чтобы этого избежать нужно использовать конструктор иначе.

function Person(name) {
    var that = {};
    that.name = name || '';
    return that;
}


Но в этом случае будет утрачена связь с прототипом, поэтому все члены, добавленные к прототипу функции Person, окажутся недоступными для объектов. Чтобы этого избежать, нужно немного видоизменить конструктор:

function Person(name) {

    if (!(this instanceof Person)) {
        return new Person(name);
    }
   
    this.name = name || '';

}

Person.prototype.getName = function () {
    return this.name;
};

var Darvin = Person('Darvin');
console.log(Darvin.getName()); // Darvin

var Einstein = new Person('Einstein');
console.log(Einstein.getName()); // Einstein


Как видно из примера, все работает как положено.
Вообще стоит подметить, при каждом вызове конструктора в памяти создаются новые функции и новые переменные (свойства). Совершенно очевидно, что это неэкономный подход к расходованию памяти, потому что реализация не изменяется от одного экземпляра к другому. Поэтому эффективней добавлять такие методы и свойства к прототипу функции-конструктора.

MyConstructor.prototype.property = true;
 

MyConstructor.prototype.getProperty = function () {
    return this.property;
};


Любой объект имеет свойство constructor, это ссылка на конструктор данного объекта:

var Adam = new Person('Adam');
var Eva = new Person('Eva');
var Cain = new Eva.constructor('Cain'); // здесь мы обращаемся к конструктору Person

Также можно обращаться и к прототипу конструктора:

Cain.constructor.prototype.child = true;

Создание объектов в форме литералов очень удобно, но при этом теряется возможность проверок следующего типа. Выше был создан объект Adam, конструктором которого является Person, значит в коде можно использовать следующую проверку:

Adam instanceof Person // true

Для объектов в форме литералов конструктор является анонимным, то есть так проверить на соответствие типу не удастся. Стоит также подметить. что в случае каскадного наследования объекты также могут подвергаться проверке, например:

var Mammal = function () {
    this.name = 'mammal';
    this.getName = function () {
        return this.name;
    };
};

var Dog = function (name) {
    this.name = name || 'dog';
};

Dog.prototype = new Mammal;

var Bob = new Dog('Bob');

console.log(Bob instanceof Dog); // true
console.log(Bob instanceof Mammal); // true


То есть проверка проходит не только по своему типу. но и по родительскому. На последок небольшая схемка. Всем успехов в Ваших делах.


javascript. Подъем функций

В javascript функцию можно построить либо на объявлении

function foo() {
  //...
}

либо на выражении

var bar = function () {
  //...
};

Функции-выражения подчеркивают тот факт, что они являются объектами. Вроде бы на этом отличий нет, но это не так. Выполним следующий кусок кода.

foo();

function foo() {
    console.log('function - declaration');
}


На выходе получим сообщение - function - declaration - то есть несмотря на то, что функция определена ниже места ее вызова, интерпретатор без проблем выполнит ее, это называется подъемом функций. Попробуем выполнить такой же кусочек, но только на основе функции-выражения.

foo();

var foo = function () {
    console.log('function - expression');
};


Хоп, на выходе получаем сообщение об ошибке

TypeError: foo is not a function

То есть в данном случае поднятия определения функции не произошло, в отличии от имени. Вот еще пример с поднятием. И в этом случае :

foo();

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};


и в этом:

foo();

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}


мы будем получать сообщение - function - declaration. Но вот если вызов происходит ниже, то картина меняется на оборот:

var foo = function foo() {
    console.log('function - expression');
};

function foo() {
    console.log('function - declaration');
}

foo();


или вот так:

function foo() {
    console.log('function - declaration');
}

var foo = function foo() {
    console.log('function - expression');
};

foo();


мы будем получать сообщение - function - expression.
Правило подъема действует и внутри самих функций. Вот небольшой примерчик:

bar();

function bar() {


    (function (global) {
        global.foo();
        foo();
    })(window);
   
    function foo() {
        console.log('local function - declaration');
    }

   
}

function foo() {
    console.log('global function - declaration');
}


На выходе получим следующие сообщения:

global function - declaration
local function - declaration
global function - declaration
local function - declaration
global function - declaration
local function - declaration

javascript. Механизм подстановки точки с запятой

Знали ли Вы что интерпретатор javascript наделен механизмом подстановки точки с запятой в конце каждой строки. Причем этот механизм работает за Вашей спиной. Поэтому, там где это должны сделать Вы, то и делайте это сами, так как тут можно столкнуться с необычными результатами. Рассмотрим пример. Напишем функцию-выражение, которая будет возвращать простой объект:

var object = function (name) {
    return
    {
        name: name || ''
    };
};


Вроде ничего такого. Но это на первый взгляд. Попробуйте получить от нее обещанный объект:

var obj = object('Leonardo'); // undefined

И если мы заглянем в этот объект, то окажется, что он не определен (возвращается undefined результат). Это связано с тем самым механизмом авто подстановки, которое после return ставит те самые точку с запятой. И в итоге неопределенное значение. Поэтому, чтобы пример отработал как положено, необходимо немного ее видоизменить:

var object = function (name) {
    return {
        name: name || ''
    };
};


Теперь все работает :)

javascript. Клонирование объектов

Ниже представлен один из способов копирования (клонирования) объектов. Как известно, простое копирование путем приравнивания объекта другой переменной невозможно, так как этой переменной будет передана всего лишь навсего ссылка на этот объект. В этом вся и проблема, так как на выходе мы должны получать независимую копию исходного объекта (клон).

var Copier = {   
    copy: function (object) {       
        var newObject;   
       
        // for copying arrays
        if (object.constructor === Array) {
            newObject = [];
            for (var i = 0, item, size = object.length; i < size; i++) {
                if (object[i].constructor === Array || object[i].constructor === Object) {
                    item = this.copy(object[i]);
                }
                else {
                    item = object[i];
                }
                newObject = newObject.concat(item);
            }           
        }
        // for copying objects
        else if (object.constructor === Object) {
            newObject = {};           
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    if (object[property].constructor === Array || object[property].constructor === Object) {
                        newObject[property] = this.copy(object[property]);
                    }
                    else {
                        newObject[property] = object[property];
                    }
                }
            }           
        }
        // for copying other elements
        else {       
            newObject = object;
        }
       
        return newObject;       
    }   
};


Данный объект Copier имеет только один метод copy, собственно который и делает всю основную работу по клонированию. На входе могут быть объекты разного типа: и массивы, и чистые объекты, и массивы, содержащие ссылки на объекты, и простые элементы, что возможно за счет глубинного рекурсивного просмотра.Стоит отметить, то в данном решении для проверки типов также можно воспользоваться следующим способом:

// if object is Object
Object.prototype.toString.call(object) === '[object Object]'

// if object is Array
Object.prototype.toString.call(object) === '[object Array]' 

Если не ошибаюсь, то такого рода проверки более предпочтительны по сравнению с такими как:

// if object is Object
object.constructor === Object
object instanceof Object

// if object is Array
object.constructor === Array
object instanceof Array

И связано это как Вы уже догадываетесь с различиями на уровне браузеров. В этом случае наше решение можно представить в следующем виде:

var Copier = {  
    copy: function (object) {      
        var newObject;  
      
        // for copying arrays
        if (this.isArray(object)) {
            newObject = [];
            for (var i = 0, item, size = object.length; i < size; i++) {
                if (this.isArray(object[i]) || this.isObject(object[i])) {
                    item = this.copy(object[i]);
                }
                else {
                    item = object[i];
                }
                newObject = newObject.concat(item);
            }          
        }
        // for copying objects
        else if (this.isObject(object)) {
            newObject = {};          
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    if (this.isArray(object[i]) || this.isObject(object[i])) {
                        newObject[property] = this.copy(object[property]);
                    }
                    else {
                        newObject[property] = object[property];
                    }
                }
            }          
        }
        // for copying other elements
        else {      
            newObject = object;
        }
      
        return newObject;      
    },
    isArray: function (object) {
        return Object.prototype.toString.call(object) === '[object Array]';
    },
    isObject: function (object) {
        return Object.prototype.toString.call(object) === '[object Object]';
    }
};


Стоит сделать небольшое замечание по поводу массивов. Так как массивы - это также объекты, то проверка типа array instanceof Object будет успешной. Нужно быть бдительным. Вообще таким образом можно проверять и все остальные типы - Function, String, Number и прочее. Всем успехов :)

javascript. Наследование

В одной из статей - Javascript. Object oriented programming. Object literal - были затронуты вопросы применения ООП подходов в javascript. Ниже приведено дополнение этой темы и касаться оно будет вопроса наследования.
У Дугласа Крокфорда есть замечательная книжка - "JavaScript. Сильные стороны". В ней собрано очень много полезных советов касательно использования javascript, очень советую. Итак, Дуглас выделяет три вида наследования. Рассмотрим каждый из них.

1. Псевдоклассовое насследование

Как Вы уже знаете, в javascript реализация классов носит условный характер (поэтому и псевдоклассовое наследование). В данном случае первоначально создаются конструкторы, содержимое которых увязываются через прототипы.

var Mammal = function(name) {
    this.name = name;
};

Mammal.prototype.getName = function() {
    return this.name;
};

Mammal.prototype.says = function() {
    return this.saying || '';
};

var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};

Cat.prototype = new Mammal();

Cat.prototype.getName = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

var myMammal = new Mammal('Herb');
var myCat = new Cat('Henrietta');

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow


2. Прототипизированное насследование

В данном случае, работа идет с объектами, это иной подход. чем в классическом виде наследования. Сначала создается ряд полезных объектов и далее на основе этих объектов строятся другие объекты.

if (typeof Object.create !== 'function') {
    Object.create = function(o) {
        var F = function() {};
        F.prototype = o;
        return new F();
    };
}

var myMammal = {
    name: 'Herb the Mammal',
    getName: function() {
        return this.name;
    },
    says: function() {
        return this.saying || '';
    }
};

var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.getName = function() {
    return this.says() + ' ' + this.name + ' ' + this.says();
};

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow


3. Функциональное наследование

В данном случае объекты создаются на основе функций, так как от них мы не инстанцируемся, то это позволяет нам отказаться от использования this во внутренних реализациях, что позволяет нам получать более надежные и более защищенные объекты (так как очень просто добавлять скрытые данные внутри этих функций) 

var mammal = function(obj) {
    var that = {};
   
    that.getName = function() {
        return obj.name;
    };
   
    that.says = function() {
        return obj.saying || '';
    };
   
    return that;
};

var cat = function(obj) {
    obj.saying = obj.saying || 'meow';
    var that = mammal(obj);
   
    that.getName = function() {
        return that.says() + ' ' + obj.name + ' ' + that.says();
    };
    return that;
};

var myMammal = mammal({name: 'Herb'});
var myCat = cat({name: 'Henrietta'});

console.log(myMammal.getName()); // Herb 
console.log(myMammal.says()); //

console.log(myCat.getName()); // meow Henrietta meow
console.log(myCat.says()); // meow

PHP5. Композиция на примере паттерна Стратегия

Один из принципов проектирования гласит - отдавайте предпочтение композиции перед наследованием. Для примера механизма композиции рассмотрим такой паттерн проектирования как Стратегия (определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. Он позволяет модифицировать алгоритмы независимо от их использования на стороне клиента). Для этого рассмотрим класс Duck, на основе которого попытаемся реализовать данный паттерн:

    abstract class Duck {
       
        protected $flyBehavior;
        protected $quackBehavior;
       
        public function __construct() {}
       
        public abstract function display();
       
        public function setFlyBehavior(FlyBehavior $fb) {
            $this->flyBehavior = $fb;
        }
       
        public function setQuackBehavior(QuackBehavior $qb) {
            $this->quackBehavior = $qb;
        }
       
        public function performFly() {
            $this->flyBehavior->fly();
        }
       
        public function performQuack() {
            $this->quackBehavior->quack();
        }
       
        public function swim() {
            echo "All ducks float, even decoys!";
            echo "<br/>";
        }
       
    }
   
    class MallardDuck extends Duck {
   
        public function __construct() {
            $this->flyBehavior = new FlyWithWings();
            $this->quackBehavior = new Quack();
        }
       
        public function display() {
            echo "I`m a real Mallard duck";
            echo "<br/>";
        }
   
    }
   
    interface FlyBehavior {
        public function fly();
    }
   
    class FlyWithWings implements FlyBehavior {
        public function fly() {
            echo "I`m flying";
            echo "<br/>";
        }
    }
   
    class FlyNoWay implements FlyBehavior {
        public function fly() {
            echo "I can`t fly";
            echo "<br/>";
        }
    }
   
    interface QuackBehavior {
        public function quack();
    }
   
    class Quack implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "Quack";
            echo "<br/>";
        }
    }
   
    class MuteQuack implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "<< Silence >>";
            echo "<br/>";
        }
    }
   
    class Squeak implements QuackBehavior {
        public function __construct() {}
        public function quack() {
            echo "Squeak";
            echo "<br/>";
        }
    }
   
    class ModelDuck extends Duck {
   
        public function __construct() {
            $this->flyBehavior = new FlyNoWay();
            $this->quackBehavior = new Quack();
        }
   
        public function display() {
            echo "I`m a model duck";
            echo "<br/>";
        }
   
    }
   
    class FlyRocketPowered implements FlyBehavior {
   
        public function fly() {
            echo "I`m flying with a rocket";
            echo "<br/>";
        }
   
    }

    $mallard = new MallardDuck();
    $mallard->performQuack();                            // Quack
    $mallard->performFly();                                // I`m flying
   
    $model = new ModelDuck();
    $model->performFly();                                // I can`t fly
    $model->setFlyBehavior(new FlyRocketPowered());
    $model->performFly();                                // I`m flying with a rocket


Более общее представление можно получить по следующему рисунку (обратите внимание на композитные связи)