Что означает в php
Перейти к содержимому

Что означает в php

  • автор:

PHP Internals

Структура данных zval(Zend value) используется для представления любых значений PHP. Zval хранит в себе само значение и тип этого значения. Это необходимо потому что PHP — это язык с динамической типизацией и поэтому тип переменных известен только во время выполнения программы (run-time), а не во время компиляции (compile-time). Кроме того, тип переменной может быть изменен в течение жизни zval, то есть zval ранее хранимый как целое число (integer) позднее может содержать строку (string).

Структура zval в PHP5 выглядит так:

typedef struct _zval_struct < zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; >zval; 

Тип переменной хранится как целочисленная метка (type tag, unsigned char). Метка может принимать одно из 8 значений, которое соответствует 8 типам данных доступных в PHP. Эти значения должны присваиваться с использованием констант вида IS_TYPE . Например, IS_NULL соответствует типу данных null, а IS_STRING — строке.

zvalue_value — это union. Union — в языке С – это структура, в которой можно объявить несколько членов разных типов, но лишь один компонент может быть доступен для использования в данный момент времени, размер union равен размеру самого большого компонента. Все компоненты объединения хранятся в памяти в одном месте и могут интерпретироваться по-разному, в зависимости от того, к кому из них вы обращаетесь. Если считать lval, то его значение будет интерпретировано как знаковое целочисленное. Значение dval будет представлено в виде числа двойной точности с плавающей запятой. И так далее. Чтобы узнать, какой компонент объединения используется в данный момент, можно посмотреть текущее значение свойства type.

typedef union _zvalue_value < long lval; // Для булевых, целочисленных и ресурсов double dval; // Для чисел с плавающей запятой struct < // Для строковых char *val; int len; >str; HashTable *ht; // Для массивов zend_object_value obj; // Для объектов zend_ast *ast; // Для констант > zvalue_value; 

refcount__gc — когда вы присваиваете переменной значение другой переменной, то они обе ссылаются на один zval, а refcount инкрементируется. Теперь, если вы захотите изменить значение одной из этих переменных, то PHP, увидя refcount больше 1, скопирует этот zval, сделает изменения там, и ваша переменная будет указывать уже на новый zval. Эта техника называется copy on write, и она позволяет неплохо снизить потребление памяти.

У подсчёта ссылок есть один серьёзный недостаток: этот механизм не способен определять циклические ссылки. Для этого в PHP используется дополнительный инструмент — циклический сборщик мусора. Каждый раз, когда значение refcount уменьшается и возникает вероятность, что zval стал частью цикла, он записывается в root buffer. Когда этот буфер заполняется, потенциальные циклы помечаются и зачищаются сборщиком мусора.

А что происходит со ссылками(пхпшными)? Все очень просто: если вы создаете ссылку от переменной, то флаг is_ref становится равным 1, и больше вышеописанная оптимизация для этого zval-а применяться не будет.

Вот список основных проблем, связанных с реализацией zval в PHP 5:

  • Zval (почти) всегда требуется размещать в куче.
  • Zval всегда требуют использования подсчёта ссылок и сбора информации о циклах. Даже в тех случаях, когда расшаривание значений не стоит потраченных ресурсов (целочисленные) или циклы не могут возникнуть в принципе.
  • Прямой подсчёт ссылок приводит к двойному выполнению этой процедуры в случае с объектами и ресурсами.
  • В некоторых случаях приходится прибегать к большому количеству обходных манёвров. Например, чтобы получить доступ к объекту, хранящемуся в переменной, необходимо суммарно разыменовать четыре указателя, со всеми сопутствующими цепочками. Об этом я тоже поговорю во второй части.
  • Прямой подсчёт ссылок также означает, что значения можно расшаривать только между zval ’ами. Например, строку невозможно совместно использовать в zval и ключе хэш-таблицы (без хранения этого ключа также в виде zval).

Zval’ы в PHP 7

In PHP 7 a zval can be reference counted or not. There is a flag in the zval structure which determined this.

There are some types which are never refcounted. These types are null, bool, int and double.

There are other types which are always refcounted. These are objects, resources and references.

And then there are types, which are sometimes refcounted. Those are strings and arrays.

For strings the not-refcounted variant is called an «interned string». If you’re using an NTS (not thread-safe) PHP 7 build, which you typically are, all string literals in your code will be interned. These interned strings are deduplicated (i.e. there is only one interned string with a certain content) and are guaranteed to exist for the full duration of the request, so there is no need to use reference counting for them. If you use opcache, these strings will live in shared memory, in which case you can’t use reference counting for them (as our refcounting mechanism is non-atomic). Interned strings have a dummy refcount of 1, which is what you’re seeing here.

For arrays the not-refcounted variant is called an «immutable array». If you use opcache, then constant array literals in your code will be converted into immutable arrays. Once again, these live in shared memory and as such must not use refcounting. Immutable arrays have a dummy refcount of 2, as it allows us to optimize certain separation paths.

В седьмой версии языка мы получили новую реализацию zval. Одним из главных нововведений стало то, что zval больше не нужно отдельно размещать в куче. Также refcount теперь хранится не в самом zval, а в любом из комплексных значений, на которые он указывает — в строках, массивах или объектах. Это даёт следующие преимущества:

  • Простые значения не требуют размещения в куче и не используют подсчёт ссылок. А хранятся или в стеке, или в составе комплексных структур.
  • Больше нет никакого двойного подсчёта. В случае с объектами используется счётчик только внутри самого объекта.
  • Поскольку refcount теперь хранится в самом значении, то оно может быть использовано независимо от самого zval. Например, строка может использоваться и в zval, и быть ключом в хэш-таблице.
  • Теперь стало гораздо меньше указателей, которые нужно перебрать, чтобы получить значение.

Вот как выглядит структура нового zval:

 zend_value value; union < struct < ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) >v; uint32_t type_info; > u1; union < uint32_t var_flags; uint32_t next; // hash collision chain uint32_t cache_slot; // literal cache slot uint32_t lineno; // line number (for ast nodes) uint32_t num_args; // arguments number for EX(This) uint32_t fe_pos; // foreach position uint32_t fe_iter_idx; // foreach iterator index >u2; >;` 

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

Также здесь есть одна небольшая проблема. Value занимает 8 байт, и благодаря своей структуре добавление даже одного байта повлечёт за собой увеличение размера zval на 16 байт. Но ведь нам не нужно целых 8 байт для хранения типа. Поэтому в zval есть дополнительное объединение u2, которое по умолчанию не используется, но может применяться для хранения 4 байт данных. Разные компоненты объединения предназначены для разных видов использования этого дополнительного хранилища.

В PHP 7 объединение value несколько отличается от пятой версии:

typedef union _zend_value < zend_long lval; double dval; zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; // Эти пока можно игнорировать, они специальные zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct < ZEND_ENDIAN_LOHI( uint32_t w1, uint32_t w2) >ww; > zend_value; 

Обратите внимание, что value теперь занимает 8 байт вместо 16. Оно хранит только целочисленные ( lval ) и числа с плавающей запятой ( dval ). Всё остальное — это указатель. Все типы указателей (за исключением специальных, отмеченных выше) используют подсчёт ссылок и содержат заголовок, определяемый zend_refcounted:

struct _zend_refcounted < uint32_t refcount; union < struct < ZEND_ENDIAN_LOHI_3( zend_uchar type, zend_uchar flags, uint16_t gc_info) >v; uint32_t type_info; > u; >; 

Я уже упоминал, что zval больше не нужно отдельно размещать в куче. Но их же нужно где-то хранить. Они всё ещё являются частью структур, размещаемых в кучах. Например, хэш-таблица будет содержать собственный zval вместо указателя на отдельный zval . Скомпилированная таблица переменных функции и таблица свойств объекта будут представлять собой zval -массивы. В качестве таких zval теперь обычно хранятся те, у которых косвенность на один уровень ниже. То есть zval ’ом теперь называется то, что раньше было zval *.

Когда-то нужно было копировать zval * и инкрементить его refcount, чтобы использовать zval в новом месте. Теперь для этого достаточно скопировать содержимое zval (игнорируя u2 ) и, может быть, инкрементить refcount того значения, на которое он указывает, если значение использует подсчёт ссылок.

Откуда PHP знает, что используется подсчёт? Это нельзя определить по одному лишь типу, поскольку некоторые типы не используют refcount — например, строки и массивы. Для этого используется один бит компонента type_info .

Внутренние типы

Какие типы поддерживаются в PHP 7:

// обычные типы данных #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 // константные выражения #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 // внутренние типы #define IS_INDIRECT 15 #define IS_PTR 17 

В чём отличия от PHP 5:

  • Тип IS_UNDEF используется вместо указателя на zval NULL (не путайте с IS_NULL zval ). Например, в приведённых выше примерах переменным назначается тип IS_UNDEF .
  • Тип IS_BOOL разделён на IS_FALSE и IS_TRUE . Поскольку такое булево значение теперь встроено в тип, это позволяет оптимизировать ряд проверок на основе типа. Данное изменение незаметно для пользователей, которые по-прежнему оперируют единственным «булевым» типом.
  • PHP-ссылки больше не используют в zval флаг is_ref . Вместо него введён новый тип IS_REFERENCE . Ниже я расскажу, как это работает.
  • IS_INDIRECT и IS_PTR являются специальными внутренними типами.

Тип IS_LONG вместо обычного long из языка С теперь использует значение zend_long . Причина в том, что в 64-битных Windows long имеет разрядность только 32 бита. Поэтому PHP 5 больше не использует в Windows в обязательном порядке 32-битные числа. А в PHP 7 вы можете использовать 64-битные значения, если система также 64-битная.

Массивы

На уровне PHP, массив — это упорядоченный список скрещенный с мэпом. Грубо говоря, PHP смешивает эти два понятия, в итоге получается, с одной стороны, очень гибкая структура данных, с другой стороны, далеко не самая оптимальная. На самом деле, для реализации массивов в PHP, используется вполне себе стандартная структура данных Hash Table. Hash Table хранит в себе указатель на самое первое и последнее значения (нужно для упорядочивания массивов), указатель на текущее значение (используется для итерации по массиву, это то, что возвращает current() ), кол-во элементов, представленных в массиве, массив указателей на Bucket-ы (о них далее), и еще кое-что.

В Hash Table есть две главные сущности, первая — это собственно сам Hash Table, и вторая — это Bucket(ведол). В ведрах хранятся сами значения, то есть на каждое значение — свое ведро. Но помимо этого в ведре хранится оригинал ключа, указатели на следующее и предыдущее ведра (они нужны для упорядочивания массива, ведь в PHP ключи могут идти в любом порядке, в каком вы захотите), и, опять же, еще кое-что.

Таким образом, когда вы добавляете новый элемент в массив, если такого ключа там еще нет, то под него создается новое ведро и добавляется в Hash Table. Как было сказано выше, у HT есть некий массив указателей на ведра, при этом ведра доступны в этом массиве по некоему индексу, а этот индекс можно вычислить зная ключ ведра.

struct _zend_array < zend_refcounted_h gc; union < struct < ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar reserve) >v; uint32_t flags; /* доступно 32 флага */ > u; uint32_t nTableMask; /* маска — nTableSize */ Bucket *arData; /* полезное хранилище данных */ uint32_t nNumUsed; /* следующая доступная ячейка в arData */ uint32_t nNumOfElements; /* общее количество занятых элементов в arData */ uint32_t nTableSize; /* размер таблицы, всегда равен двойке в степени */ uint32_t nInternalPointer; /* используется для итерации */ zend_long nNextFreeElement; /* следующий доступный целочисленный ключ */ dtor_func_t pDestructor; /* деструктор данных */ >; 

Самое интересное поле данных — arData, это своеобразный указатель на область памяти цепочки Bucket. Сама Bucket представляет собой одну ячейку в массиве:

typedef struct _Bucket < zval val; /* значение */ zend_ulong h; /* хэш (или числовой индекс) */ zend_string *key; /* строковый ключ или NULL для числовых значений */ >Bucket; 

Как вы могли заметить, в структуре Bucket будет храниться zval. Обратите внимание, что здесь используется не указатель на zval, а именно сама структура. Так сделано потому, что в РНР 7 zval’ы больше не размещаются в куче (в отличие от PHP 5), но при этом в РНР 7 может размещаться целевое значение, хранящееся в zval в виде указателя (например, строка РНР).

Стоит отметить, что в PHP почти все посторено на одной этой структуре HashTable: все переменные, лежащие в каком-либо scope-е, на самом деле лежат в HT, все методы классов, все поля классов, даже сами дефинишины классов лежат в HT, это на самом деле очень гибкая структура. Помимо прочего, HT обеспечивает практически одинаковую скорость выборки/вставки/удаления и сложность всех троих является O(1), но с оговоркой на небольшой оверхед при коллизиях.

Свойства хэш таблицы:

  • Ключ может быть строкой или целочисленным. В первом случае используется структура zend_string , во втором — zend_ulong .
  • Хэш-таблица всегда должна помнить порядок добавления её элементов.
  • Размер хэш-таблицы меняется автоматически. В зависимости от обстоятельств она самостоятельно уменьшается или увеличивается.
  • С точки зрения внутренней реализации размер таблицы всегда равен двойке в степени. Это делается для улучшения производительности и выравнивания размещения данных в памяти.
  • Все значения в хэш-таблице хранятся в структуре zval , больше нигде. Zval ’ы могут содержать данные любых типов.

Разрешение коллизий

Теперь разберёмся, как разрешаются коллизии. Как вы помните, в хэш-таблице несколько ключей при хэшировании и сжатии могут соответствовать одному и тому же индексу преобразования. Так что, получив индекс преобразования, мы с его помощью извлекаем данные обратно из arData и сравниваем хэши и ключи, проверяя, то ли это, что нужно. Если данные неправильные, мы проходим по связному списку с помощью поля zval.u2.next , в котором отражается следующая ячейка для внесения данных.

Обратите внимание, что связный список не рассеян по памяти, как традиционные связные списки. Вместо того чтобы ходить по нескольким размещённым в памяти указателям, полученным от кучи — и наверняка разбросанным по адресному пространству, — мы считываем из памяти полный вектор arData И это одна из главных причин увеличения производительности хэш-таблиц в РНР 7, а также всего языка.

В РНР 7 у хэш-таблиц очень высокая локальность данных. В большинстве случаев доступ происходит за 1 наносекунду, поскольку данные обычно находятся в процессорном кэше первого уровня.

Строки

В PHP 7 строки представляются с помощью типа zend_string :

struct _zend_string < zend_refcounted gc; zend_ulong h; /* hash value */ size_t len; char val[1]; >; 

Помимо содержащегося в заголовке refcounted , здесь также используется кэш хэша h, длина len и значение val . Кэш хэша используется для того, чтобы не пересчитывать хэш строки при каждом обращении к HashTable . При первом использовании он инициализируется как ненулевой хэш. Если вы не слишком хорошо знакомы с разнообразными хаками в языке С, то определение val может показаться странным: он объявляется как массив символов с одним-единственным элементом. Но мы же наверняка захотим хранить строки длиной больше, чем один символ. Здесь используется метод под названием «структурный хак» (struct hack): массив хоть и объявляется с одним элементом, но при создании zend_string мы определим возможность хранения более длинной строки. Кроме того, можно будет получить доступ к более длинным строкам с помощью val .

Новая реализация строковой переменной имеет ряд преимуществ перед обычными строками в языке С. Во-первых, в неё теперь интегрирована длина, которая больше не «болтается» где-то поблизости. Во-вторых, в заголовке используется подсчёт ссылок, поэтому стало возможным использовать строки в разных местах без применения zval . Это особенно важно для расшаривания ключей хэш-таблицы.

Но есть и большая ложка дёгтя. Получить из zend_string строку языка С легко (с помощью str->val), а вот из С-строки напрямую получить zend_string нельзя. Для этого придётся скопировать значение строки в заново созданный zend_string. Особенно досадно, когда дело доходит до работы с текстовыми строками (literal string), то есть постоянными строками (constant string), встречающимися в исходном С-коде.

Изолированные (interned) строки — это такие строки, которые не уничтожаются до завершения запроса и потому не нуждаются в использовании счётчика ссылок. Они дедуплицированы, поэтому при создании новой изолированной строки движок сначала проверяет, нет ли другой с таким же значением. Вообще все строки, имеющиеся в PHP-коде (включая переменные, названия функций и т.д.), обычно являются изолированными. Неизменяемые строки — это изолированные строки, созданные до начала запроса. Они не уничтожаются по окончании запроса, в отличие от изолированных.

Если используется OPCache, то изолированные строки будут храниться в общей памяти (SHM) и использоваться всеми PHP-процессами. В этом случае неизменяемые строки становятся бесполезными, поскольку изолированные и так не будут уничтожены.

Классы и объекты

  • Классы занимают много памяти.
  • Внутренние классы гораздо лучше оптимизированы по сравнению с пользовательскими, потому что последние должны быть созданы и уничтожены при каждом запросе. Внутренние классы существуют постоянно.
  • Классы, интерфейсы и трейты используют одни и те же структуру и процедуры, различия очень малы.
  • Во время наследования или объявления процесс привязки сильно и долго нагружает процессор, но памяти задействуется немного, поскольку многие вещи не дуплицируются, а используются совместно. Кроме того, лучше запускать привязку классов во время компиляции.

PHP7 отказались от двойного подсчёта ссылок, уменьшили потребление памяти и количество косвенной адресации. Так выглядит новая структура zend_object:

struct _zend_object < zend_refcounted gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; >; 

Эта структура — почти всё, что осталось от объекта. zend_object_value, заменённый прямым указателем на объект и хранилище объектов, хоть и не исключён совсем, но на глаза попадается куда реже.

Улучшение производительности PHP7

  • Упакованные массивы — Они потребляют меньше памяти и во многих случаях работают гораздо быстрее традиционных массивов. Упакованные массивы должны удовлетворять критериям:
    • Ключи — только целочисленные значения;
    • Ключи вставляются в массив только по возрастанию.

    Не забывайте: Если вам нужен список, то не используйте строки в ключах (это не даст применять оптимизацию упакованных массивов); Если ключи в списке только целочисленные, постарайтесь распределить их по возрастанию (в противном случае оптимизация тоже не сработает).

    • http://www.phpinternalsbook.com/
    • https://romka.gitbooks.io/php-internals-book-ru/
    • Как устроены переменные в PHP
    • Как устроены массивы в PHP
    • Внутреннее представление значений в PHP7 (часть 1)
    • Внутреннее представление значений в PHP 7 (часть 2)
    • Массивы в РНР 7: хэш-таблицы
    • Подробно об объектах и классах в PHP
    • Объекты в PHP 7
    • Улучшение производительности PHP 7
    • Обзор расширения OPCache для PHP
    • Трамплин вызова магических функций в PHP 7

    PHP 8.1: оператор new в инициализаторах

    PHP 8.1: оператор new в инициализаторах

    Так в чем же польза от оператора new в инициализаторах? Давайте посмотрим на пример, раньше мы все писали такой код:

    class MyStateMachine < public function __construct( private ?State $state = null, ) < $this->state ??= new InitialState(); > >

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

    class MyStateMachine < public function __construct( private string $state = 'initial', ) < >>

    Таким образом, с PHP 8.1 мы можем использовать тот же синтаксис «значения по умолчанию» и для объектов. Другими словами: вы можете использовать new для аргументов по умолчанию (которые являются одним из примеров «инициализаторов»):

    class MyStateMachine < public function __construct( private State $state = new InitialState(), ) < >>

    «Инициализаторы» — это больше, чем значения параметров по умолчанию, вот простое объяснение из RFC:

    Этот RFC предлагает разрешить использование новых выражений внутри значений параметров по умолчанию, аргументов атрибутов, инициализаторов статических переменных и инициализаторов глобальных констант.

    Да, вы правильно прочитали: атрибуты тоже есть в этом списке! Представьте себе простую библиотеку проверки, которая использует атрибуты для проверки ввода свойств. Возможно, она должна иметь возможность проверять элементы массива, примерно так:

    class CreateEmailsRequest extends FormRequestData

    До PHP 8.1 вы не могли писать такой код, потому что вам не разрешалось использовать new в атрибутах из-за способа их оценки, но теперь вы можете!

    Давайте взглянем на некоторые важные детали, о которых стоит упомянуть.

    Создание только при необходимости

    Такого рода «новые значения» будут создаваться только тогда, когда это действительно необходимо. Это означает, что в нашем первом примере PHP создаст только новый объект, если InitialState не содержит аргументов:

    class MyStateMachine < public function __construct( private State $state = new InitialState(), ) < >> new MyStateMachine(new DraftState()); // InitialState не создается new MyStateMachine(); // Сейчас создается

    В случае атрибутов, например, объекты будут созданы только при newInstance вызове атрибута отражения.

    Не в свойствах класса

    Также вы должны знать, что не можете использовать new значение по умолчанию в свойствах класса. Поддержка этой функции привела бы к множеству непредвиденных побочных эффектов, например, при сериализации и десериализации объектов:

    class MyStateMachine

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

    class MyStateMachine < private State $state; public function __construct( State $state = new InitialState(), ) < $this->state = $state; > >

    Ограниченный ввод

    Возможно, вы уже догадались, но вы можете передать только ограниченный набор входных данных при создании новых объектов в инициализаторах. Например, вы не можете использовать переменные, оператор распаковки, анонимные классы и т.д.

    Функции в PHP

    В лекции рассматриваются: понятие функции, функции, определяемые пользователем, аргументы функций, передача аргументов по значению и по ссылке, значение аргументов по умолчанию и значения, возвращаемые функцией (функция return()). Пример — создание web-интерфейс для генерации HTML-формы.

    В этой лекции будут рассматриваться вопросы создания и использования функций в PHP. Говоря «функции», мы не имеем в виду все существующие в PHP функции, а лишь функции, определяемые пользователем. Мы рассмотрим способы задания таких функций, методы передачи аргументов, использование аргументов со значением по умолчанию и значения, возвращаемые функцией.

    В качестве примера создадим web-интерфейс для генерации html-формы. То есть пользователь выбирает, не прибегая к программированию, какие элементы формы нужно создать, и характеристики этих элементов, а наша программа генерирует нужную форму.

    Функции, определяемые пользователем

    Для чего нужны функции? Чтобы ответить на этот вопрос, нужно понять, что вообще представляют собой функции. В программировании, как и в математике, функция есть отображение множества ее аргументов на множество ее значений. То есть функция для каждого набора значений аргумента возвращает какие-то значения, являющиеся результатом ее работы. Зачем нужны функции, попытаемся объяснить на примере. Классический пример функции в программировании – это функция, вычисляющая значение факториала числа. То есть мы задаем ей число, а она возвращает нам его факториал. При этом не нужно для каждого числа, факториал которого мы хотим получить, повторять один и тот же код – достаточно просто вызвать функцию с аргументом, равным этому числу.

    Функция вычисления факториала натурального числа

     echo fact(3); // можно было бы написать echo (3*2); // но если число большое, echo fact(50); // то удобнее пользоваться функцией, // чем писать echo (50*49*48*. *3*2); ?>

    Таким образом, когда мы осуществляем действия, в которых прослеживается зависимость от каких-то данных, и при этом, возможно, нам понадобится выполнять такие же действия, но с другими исходными данными, удобно использовать механизм функций – оформить блок действий в виде тела функции , а меняющиеся данные – в качестве ее параметров.

    Посмотрим, как в общем виде выглядит задание (объявление) функции. Функция может быть определена с помощью следующего синтаксиса:

    function Имя_функции (параметр1, параметр2, . параметрN)

    Если прямо так написать в php-программе, то работать ничего не будет. Во-первых, Имя_функции и имена параметров функции ( параметр1 , параметр2 и т.д.) должны соответствовать правилам наименования в PHP (и русских символов в них лучше не использовать). Имена функций нечувствительны к регистру. Во-вторых, параметры функции – это переменные языка, поэтому перед названием каждого из них должен стоять знак $. Никаких троеточий ставить в списке параметров нельзя. В-третьих, вместо слов блок_действий в теле функции должен находиться любой правильный PHP-код (не обязательно зависящий от параметров). И наконец, после ключевого слова return должно идти корректное php-выражение (что-либо, что имеет значение). Кроме того, у функции может и не быть параметров, как и возвращаемого значения. Пример правильного объявления функции – функция вычисления факториала, приведенная выше.

    Как происходит вызов функции? Указывается имя функции и в круглых скобках список значений ее параметров, если таковые имеются:

    Когда можно вызывать функцию? Казалось бы, странный вопрос. Функцию можно вызвать после ее определения, т.е. в любой строке программы ниже блока function f_name() <. >. В PHP3 это было действительно так. Но уже в PHP4 такого требования нет. Все дело в том, как интерпретатор обрабатывает получаемый код. Единственное исключение составляют функции, определяемые условно (внутри условных операторов или других функций). Когда функция определяется таким образом, ее определение должно предшествовать ее вызову.

    Хочу изучать Python
    "; > > // теперь можно вызывать Make_event() Make_event(); // определение функции Save_info function Save_info($first, $last, $message)< echo "
    $message
    "; echo "Имя: ". $first . " ". $last . "
    "; > Save_info("Федя","Федоров", "А я выбрал Lisp"); // Save_info можно вызывать и здесь ?>

    Пример 5.1. Определение функции внутри условного оператора

    Если функция однажды определена в программе, то переопределить или удалить ее позже нельзя. Не смотря на то что имена функций нечувствительны к регистру, лучше вызывать функцию по тому же имени, каким она была задана в определении.

    Пример 5.2. Определение функции внутри функции

    Рассмотрим подробнее аргументы функций, их назначение и использование.

    Аргументы функций

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

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

    Когда аргумент передается в функцию по значению, изменение значения аргумента внутри функции не влияет на его значение вне функции. Чтобы позволить функции изменять ее аргументы, их нужно передавать по ссылке. Для этого в определении функции перед именем аргумента следует написать знак амперсант «&» .

     $str ; // пусть имеется такая строка echo $str .">
    "; // выведет элемент формы – // не отмеченную радио кнопку add_label($str); // вызовем функцию echo $str .">
    "; // это выведет уже отмеченную // радио кнопку ?>

    Пример 5.3. Передача аргументов по ссылке

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

    У нас есть функция, создающая информационное сообщение, подпись к которому меняется в зависимости от значения переданного ей параметра. Если значение параметра не задано, то используется подпись «Оргкомитет» .

    "; > Message(); // вызываем функцию без параметра. // В этом случае подпись – это Оргкомитет Message("С уважением, Вася"); // В этом случае подпись // будет "С уважением, Вася" ?>

    Пример 5.4. Значения аргументов по умолчанию

    Результатом работы этого скрипта будет:

    Следующее собрание состоится завтра. Оргкомитет. Следующее собрание состоится завтра. С уважением, Вася.

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

    Например, мы хотим внести описание статьи в каталог. Пользователь должен ввести такие характеристики статьи, как ее название, автор и краткое описание. Если пользователь не вводит имя автора статьи, считаем, что это Иванов Иван.

    Краткое описание: " echo "$description 
    "; > Add_article("Информатика и мы", "Это статья про информатику . ", "Петров Петр"); Add_article("Кто такие хакеры", "Это статья про хакеров . "); ?>

    В результате работы скрипта получим следующее

    Заносим в каталог статью: Информатика и мы, автор Петров Петр. Краткое описание: Это статья про информатику. Заносим в каталог статью: Кто такие хакеры, автор Иванов Иван. Краткое описание: Это статья про хакеров.

    Если же мы напишем вот так:

     Add_article("Кто такие хакеры", "Это статья про хакеров. "); ?>

    То в результате получим:

    Warning: Missing argument 3 for add_article() in c:\users\nina\tasks\func\def_bad.php on line 2
    Списки аргументов переменной длины

    В PHP4 можно создавать функции с переменным числом аргументов. То есть мы создаем функцию, не зная заранее, со сколькими аргументами ее вызовут. Для написания такой функции никакого специального синтаксиса не требуется. Все делается с помощью встроенных функций func_num_args() , func_get_args() , func_get_args() .

    Функция func_num_args() возвращает число аргументов, переданных в текущую функцию. Эта функция может использоваться только внутри определения пользовательской функции. Если она появится вне функции, то интерпретатор выдаст предупреждение.

     DataCheck(); // выведет строку // "Число аргументов функции 0" DataCheck(1,2,3); // выведет строку // "Число аргументов функции 3" ?>

    Пример 5.5. Использование функции func_num_args()

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

    Номер_аргумента не может превышать число аргументов, переданных в функцию. Иначе будет сгенерировано предупреждение, и функция func_get_arg() возвратит False .

    Создадим функцию для проверки типа данных, ее аргументов. Считаем, что проверка прошла успешно, если первый аргумент функции – целое число, второй – строка.

    =1) if (!is_int(func_get_arg(0))) $check = false; /* проверяем, является ли второй переданный аргумент строкой */ if ($n>=2) if (!is_string(func_get_arg(1))) $check = false; return $check; > if (DataCheck(a123,"text")) echo "Проверка прошла успешно
    "; else echo "Данные не удовлетворяют условиям
    "; if (DataCheck(324)) echo "Проверка прошла успешно
    "; else echo "Данные не удовлетворяют условиям
    "; ?>

    Пример 5.6. Функция для проверки типа данных, ее аргументов

    Результатом работы будет следующее.

    Данные не удовлетворяют условиям Проверка прошла успешно

    Функция func_get_args() возвращает массив, состоящий из списка аргументов, переданных функции. Каждый элемент массива соответствует аргументу, переданному функции. Если функция используется вне определения пользовательской функции, то генерируется предупреждение.

    Перепишем предыдущий пример, используя эту функцию. Будем проверять, является ли целым числом каждый четный аргумент, передаваемый функции:

     > return $check; > if (DataCheck(text, 324)) echo "Проверка прошла успешно
    "; else echo "Данные не удовлетворяют условиям
    "; ?>

    Как видим, комбинации функций func_num_args() , func_get_arg() и func_get_args() используется для того, чтобы функции могли иметь переменный список аргументов. Эти функции были добавлены только в PHP 4. В PHP3 для того, чтобы добиться подобного эффекта, можно использовать в качестве аргумента функции массив. Например, вот так можно написать скрипт, проверяющий, является ли каждый нечетный параметр функции целым числом:

     > return $check; > if (DataCheck(text, 324)) echo "Проверка прошла успешно
    "; else echo "Данные не удовлетворяют условиям
    "; ?>
    Использование переменных внутри функции
    Глобальные переменные

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

    global $var1, $var2;
     echo "вне функции $a=',$a,', "; Test_g(); echo "
    "; echo "вне функции $a=',$a,', "; Test_g(); ?>

    Пример 5.7. Глобальные переменные

    В результате работы этого скрипта получим:

    вне функции $a=1, в результате работы функции $a=2 вне функции $a=2, в результате работы функции $a=4

    Когда переменная объявляется как глобальная, фактически создается ссылка на глобальную переменную . Поэтому такая запись эквивалентна следующей (массив GLOBALS содержит все переменные, глобальные относительно текущей области видимости):

    $var1 = & $GLOBALS["var1"]; $var2 = & $GLOBALS["var2"];

    Это значит, например, что удаление переменной $var1 не удаляет глобальной переменной $_GLOBALS[«var1»] .

    Статические переменные

    Чтобы использовать переменные только внутри функции, при этом сохраняя их значения и после выхода из функции, нужно объявить эти переменные как статические. Статические переменные видны только внутри функции и не теряют своего значения, если выполнение программы выходит за пределы функции. Объявление таких переменных производится с помощью ключевого слова static :

    static $var1, $var2;

    Статической переменной может быть присвоено любое значение, но не ссылка.

     Test_s(); // выведет 2 echo $a; // ничего не выведет, так как // $a доступна только // внутри функции Test_s(); // внутри функции $a=2, поэтому // результатом работы функции // будет число 4 ?>

    Пример 5.8. Использование статической переменной

    Возвращаемые значения

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

    Например, составим функцию, которая возвращает возраст человека. Если человек не умер, то возраст считается относительно текущего года.

     echo Age(1971, false); // выведет 33 echo Age(1971, 2001); // выведет 30 ?>

    В этом пример можно было и не использовать функцию return , а просто заменить ее функцией вывода echo . Однако если мы все же делаем так, что функция возвращает какое-то значение (в данном случае возраст человека), то в программе мы можем присвоить любой переменной значение этой функции:

    $my_age = Age(1981, 2004);

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

    $b_month && date("d")>$b_day) < $day = date ("d") – $b_day; $month = date("m") – $b_month; $year = date("Y") – $b_year; >else < $year = date("Y") – $b_year – 1; $day = 31 – $b_day – date ("d"); $month = 12 – ($b_month – date("m")); >return array ($day,$month,$year); > $age = Full_age("07","08","1974"); echo "Вам $age[2] лет, $age[1] месяцев и $age[0] дней"; // выведет "Вам 29 лет, 11 месяцев и 5 дней" ?>

    Когда функция возвращает несколько значений для их обработки в программе, удобно использовать языковую конструкцию list (), которая позволяет одним действием присвоить значения сразу нескольким переменным. Например, в предыдущем примере, оставив без изменения функцию, обработать возвращаемые ей значения можно было так:

    Вообще конструкцию list () можно использовать для присвоения переменным значений элементов любого массива.

    Пример 5.9. Использование list()

    Возвращение ссылки

    В результате своей работы функция также может возвращать ссылку на какую-либо переменную. Это может пригодиться, если требуется использовать функцию для того, чтобы определить, какой переменной должна быть присвоена ссылка. Чтобы получить из функции ссылку, нужно при объявлении перед ее именем написать знак амперсант ( & ) и каждый раз при вызове функции перед ее именем тоже писать амперсант ( & ). Обычно функция возвращает ссылку на какую-либо глобальную переменную (или ее часть – ссылку на элемент глобального массива), ссылку на статическую переменную (или ее часть) или ссылку на один из аргументов, если он был также передан по ссылке.

     $var =& ref(4); echo $var, " и ", $b, "
    "; //выведет 2 и 2 $b = 10; echo $var, " и ", $b, "
    "; // выведет 10 и 10 ?>

    Пример 5.10. Возвращение ссылки

    При использовании синтаксиса ссылок в переменную $var нашего примера не копируется значение переменной $b возвращенной функцией $ref , а создается ссылка на эту переменную. То есть теперь переменные $var и $b идентичны и будут изменяться одновременно.

    Переменные функции

    PHP поддерживает концепцию переменных функций . Это значит, что если имя переменной заканчивается круглыми скобками, то PHP ищет функцию с таким же именем и пытается ее выполнить.

     function Show_text()< echo "Отправить сообщение по почте
    "; > $func = "Show_text"; // создаем переменную со значением, // равным имени функции Show_text $func(); // это вызовет функцию Show_text $func = "Add_sign"; // создаем переменную со значением, // равным имени функции Add_sign $func("Hello, everybody
    "); // это вызовет функцию // Add_sign с параметром "Привет всем" ?>

    Пример 5.11. Использование переменных функций

    В этом примере функция Show_text просто выводит строку текста. Казалось бы, зачем для этого создавать отдельную функцию, если существует специальная функция echo() . Дело в том, что такие функции, как echo() , print() , unset() , include() и т.п. нельзя использовать в качестве переменных функций . То есть если мы напишем:

    то интерпретатор выведет ошибку:

    Fatal error: Call to undefined function: echo() in c:\users\nina\tasks\func\var_f.php on line 2

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

    Внутренние (встроенные) функции

    Говоря о функциях, определяемых пользователем, все же нельзя не сказать пару слов о встроенных функциях . С некоторыми из встроенных функций , такими как echo() , print() , date() , include() , мы уже познакомились. На самом деле все перечисленные функции, кроме date() , являются языковыми конструкциями. Они входят в ядро PHP и не требуют никаких дополнительных настроек и модулей. Функция date() тоже входит в состав ядра PHP и не требует настроек. Но есть и функции, для работы с которыми нужно установить различные библиотеки и подключить соответствующий модуль. Например, для использования функций работы с базой данных MySql следует скомпилировать PHP с поддержкой этого расширения. В последнее время наиболее распространенные расширения и соответственно их функции изначально включают в состав PHP так, чтобы с ними можно работать без каких бы то ни было дополнительных настроек интерпретатора.

    Решение задачи

    Напомним, в чем состоит задача. Мы хотим написать интерфейс, который позволял бы создавать html-формы. Пользователь выбирает, какие элементы и в каком количестве нужно создать, придумывает им названия, а наша программа сама генерирует требуемую форму.

    Разобьем задачу на несколько подзадач: выбор типов элементов ввода и их количества, создание названий элементов ввода и обработка полученных данных, т.е. непосредственно генерация формы. Первая задача достаточно проста: нужно написать соответствующую форму, например подобную приведенной ниже (task_form.html):

    Листинг 5.12. task_form.html

    Когда мы пишем в имени элемента формы, например types[] , это значит, что его имя – следующий элемент массива types . То есть у нас первый элемент формы ( «строка ввода текста» ) будет иметь имя types[0] , а второй (текстовая область) – types[1] . В браузере task_form.html будет выглядеть примерно так:

    Рис. 5.1. Форма для выбора создаваемых элементов и их количества

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

    '; foreach ($_GET["types"] as $k => $type) < /* перебираем все типы элементов, которые нужно создать */ $num = $_GET["numbers"][$type]; // сколько элементов каждого типа нужно for ($i=1;$i<=$num;$i++)< // создаем $num строк для ввода $st.= "Введите имя $i-го элемента типа $type: "; $st. ; >// сохраняем тип и число необходимых // элементов ввода этого типа $st. ; $st. ; > $st . ; return $st; // в переменной $st содержится код формы // для запроса имен > else echo "Select type"; > echo Ask_names(); // вызываем функцию и выводим // результаты ее работы ?>

    Листинг 5.13. ask_names.php

    Допустим, нужно создать два элемента типа «текстовая строка» и один элемент типа «текстовая область», как и отмечено в форме выше. Тогда скрипт ask_names.php обработает ее таким образом, что мы получим такую форму:

    Рис. 5.2. Форма для ввода названий создаваемых элементов

    Введем в эту форму, например, строки «Название», «Автор» и «Краткое содержание». Эти данные будет обрабатывать скрипт task.php.

     return $str; > function Create_form()< // функция создает форму // с нужными элементами global $show_file; $str = ''; foreach ($_GET["types"] as $k => $type) < // перебираем типы элементов $num = $_GET["numbers"][$k]; // число элементов этого типа for ($i=1;$i<=$num;$i++)< $arr = $_GET["names"][$type][$i-1]; // имя создаваемого элемента $str .= Create_element($type,$arr); // вызываем функцию для // создания элемента >> $str . ; echo $str; > $crt = "Create_form"; $crt(); // вызываем функцию создания // формы Create_form ?>

    Листинг 5.14. task.php

    Результатом работы этого скрипта с входными данными, приведенными выше, будет следующая форма:

    Рис. 5.3. Пример формы, сгенерированной нашей программой

      &copy INTUIT.ru. Автор: Н.В. Савельева. Курс размещен по договоренности с администрацией INTUIT.ru

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *