Объектно-ориентированный php с классами и объектами

Методы

Так же как свойства позволяют объектам сохранять данные, методы позволяют объектам выполнять задачи. Методы (methods) — это специальные функции, которые объявляются внутри класса. Как и можно было ожидать, объявление метода напоминает объявление функции. За ключевым словом function следует имя метода, а за ним — необязательный список переменных-аргументов в круглых скобках. Тело метода заключается в фигурные скобки:

public function myMethod( $arg1, $arg2, ... argN )
{ 
	// ...
}

В отличие от функций, методы необходимо объявлять в теле класса. При этом можно также указывать ряд спецификаторов, включая ключевое слово, определяющее видимость метода. Как и свойства, методы можно определять как public, protected или private. Объявляя метод как public, мы тем самым обеспечиваем возможность его вызова извне текущего объекта. Если в определении метода вы опустите ключевое слово, определяющее видимость, то метод будет объявлен неявно как public. К модификаторам методов мы вернемся в следующей статье.

В большинстве случаев метод вызывают с помощью объектной переменной, за которой указываются символы ‘->’ и имя метода. При вызове метода нужно использовать круглые скобки, так же как при вызове функции (даже если методу не передаются никакие аргументы). Давайте добавим методы к определенному ранее классу ShopProduct:

Код PHP

В результате на выходе получим результат аналогичный предыдущему.

Мы добавили метод getProducer() к классу ShopProduct

Обратите внимание на то, что при определении метода мы не включили ключевое слово, определяющее его видимость. Это означает, что метод getProducer() относится к типу public и его можно вызвать из-за пределов класса

При определении метода getProducer() мы воспользовались новой возможностью — псевдопеременной $this. Она представляет собой механизм, посредством которого класс может обратиться к экземпляру объекта. Если вы считаете, что это трудно для понимания, попробуйте заменить $this «текущим экземпляром объекта». Тогда оператор $this->producerFirstName превратится в свойство $producerFirstName текущего экземпляра объекта. Так, метод getProducer() объединяет и возвращает значения свойств $producerFirstName и $producerMainName, избавляя нас от неприятной работы всякий раз, когда нужно вывести полное имя автора.

Итак, нам удалось немного улучшить наш класс. Но для него по-прежнему характерна слишком большая «гибкость». Мы полагаемся на то, что программист будет изменять стандартные значения свойств объекта ShopProduct. Но это проблематично в двух отношениях. Во-первых, нужно пять строк кода, чтобы должным образом инициализировать объект типа ShopProduct, и ни один программист вам не скажет за это спасибо. Во-вторых, у нас нет способа гарантировать, что какое-либо свойство будет определено при инициализации объекта ShopProduct. Поэтому нам нужен метод, который будет вызываться, автоматически при создании экземпляра объекта на основе класса.

Proposal

The proposal is to introduce a new magic method to classes implementing the above defined requirements for a static class constructor.

Code example of the method:

<?php
    class Example {
        ...
 
	/**
         * Static class constructor is called on first call to either 
         * object constructor or any public or protected static method 
         * or property of this class.
         *
         * @param void
         * @return void
         * @throws Exception 
         */
        private static function __static() {
            //Initialize static properties.
        }
 
        ...
    }
?>

Method details and explanation for decisions:

— Name: __static
I have looked to several other oop-languages already having this class constructor. For example C# calls it “cctor” and Java calls it just “static” some call it initStatic. I prefer the “static of Java because I think that explains better then “cctor” for what purpose it should be used and is shorter to write then “initStatic”

— Accesibility: private
To keep the responsibility encapsulated into the class

— Context: static
For being inside class context

— Parameters: void
Because the language calling it, does not know any parameters

— Return-Type: void

— Throws: Exception in case of an error

— Trigger for “magic” method call: First call to class, either first call to __construct(…) or first call to any public or protected static method or property of the class

Статические поля класса

Мы рассмотрели статические переменные в статье PHP Variable Scope: All You Need to Know. Как обычная локальная переменная, статическая переменная доступна только в пределах функции. Тем не менее, в отличие от обычных локальных, статические переменные сохраняют значения между вызовами функции.

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

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

Чтобы создать статическую переменную, добавьте ключевое слово static в ее задании:

class MyClass {
 public static $myProperty;
}

Вот пример того, как работают статические переменные:

class Member {

 private $username;
 public static $numMembers = 0;

 public function __construct( $username ) {
   $this->username = $username;
   self::$numMembers++;
 }
}

echo Member::$numMembers . "<br>";  // отобразит "0"
$aMember = new Member( "fred" );
echo Member::$numMembers . "<br>";  // отобразит "1"
$anotherMember = new Member( "mary" );
echo Member::$numMembers . "<br>";  // отобразит "2"

Есть несколько интересных вещей, так что давайте разберем данный скрипт:

  • В классе Member два поля: частное поле $username и статическое $numMembers, которое изначально получает значение 0;
  • Конструктор получает в качестве параметра аргумент $username и устанавливает полю только что созданного объекта значение этого параметра. В то же время, он инкрементирует значение поля $numMembers, тем самым давая понять, что число объектов нашего класса увеличилось на 1.

Отметьте, что конструктор обращается к статическому полю так: self::$numMembers. Ключевое слово self похоже на $this, которое мы рассмотрели в прошлом уроке. Тогда как $this ссылается на текущий объект, self — на текущий класс. Также тогда как для получения доступа к полям и методам объекта вы используете ->, то в этом случае используйте :: для получения доступа к полям и методам класса.

В завершении скрипт создает несколько объектов класса Member и отображает на странице их количество, т.е. значение статической переменной $numMembers. Отметьте, что данная переменная сохраняет свое значение на протяжении всей работы скрипта, несмотря на объекты класса.

Итак, чтобы получить доступ к статическому полю класса, применяйте оператор ::. Здесь мы не можем воспользоваться ключевым словом self, так как код находится за пределами класса, поэтому мы пишем имя класса, затем ::, а затем имя поля (Member::$numMembers). В пределах конструктора тоже нужно использовать именно такую структуру, а не self.

На заметку: нашему скрипту ничего не стоило получить доступ к полю класса $numMembers перед тем, как создался первый объект данного класса. Нет необходимости создавать объекты класса для того, чтобы пользоваться его статическими полями.

PHP 7.0

Анонимный класс может использоваться вместо именованного класса:

Когда класс не нужно документировать

Когда класс во время выполнения используется только один раз

Функция целочисленного деления — безопасный способ деления (даже на 0).

Возвращает целочисленный результат деления первого операнда на второй. Если делитель (второй операнд) равен нулю, функция пробрасывает EWARNING и возвращает FALSE.

Добавлен новый оператор объединения с null — «??»

Добавлен новый оператор — spaceship (космический корабль) (<=>)

Используется для оптимизации и упрощения операций сравнения.

Это всего лишь первый шаг к реализации преимуществ более строго типизированного языка программирования в PHP — v0.5.

Добавлена ​​возможность возвращать типы помимо скалярных — классы, включая наследование. Хех, упустив при этом возможность сделать это необязательным (что будет введено в v7.1 :))

Групповые объявления use

В теле функций генераторов разрешен следующий новый синтаксис:

PHP 7 почти вдвое быстрее, чем PHP 5.6.

Значительное сокращение использования памяти

Как видно из диаграмм, PHP 7.0 стал громадным шагом вперед с точки зрения производительности и использования памяти. Для страницы с запросами к базе данных версия 7.0.0 более чем в 3 раза быстрее, чем 5.6 с включенным opcache в 2.7 раза быстрее без opcache! С точки зрения использования памяти разница тоже существенная!

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

и теперь реализуют .

Иерархия :

Внимание! Вы можете реализовать только через и. Синтаксис кодирования Unicode — “\u{xxxxx}”

Синтаксис кодирования Unicode — “\u{xxxxx}”

С этим нововведением глобально зарезервированные слова стали полу-зарезервированными:

За исключением того, что по-прежнему запрещено определять константу класса с именем из-за разрешения имени класса .

Понимание объекты и классы

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

Узнаем различия между объектами и классами

Фотографии от Instant Jefferson и John Wardell

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

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

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

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

Структурирование классов

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

После создания класса новый экземпляр может быть создан и сохранен в переменной с использованием ключевого слова :

Чтобы увидеть содержимое класса, используйте

Попробуйте выполнить этот процесс, разместив весь предыдущий код в новом файле в папке для тестирования:

Загрузите страницу в браузере по адресу , и на экране должно появиться следующее:

В своей простейшей форме вы только что завершили свой первый ООП-скрипт.

vba — более быстрый способ раскрашивания ячеек таблицы в Word

Классы

Класс — это шаблон, по которому создаются объекты.

Напомню, что классы — это описания объектов. Мы не можем создать объект «на лету», как это происходит с массивами. Объект может быть создан только на основе своего описания — класса. Этим, кстати, реализация объектов в PHP отличается от JavaScript. В JS объектам не нужны никакие классы, и они могут быть созданы и модифицированы когда угодно и как угодно.

Класс как чертёж

Зачем же нужны классы, и почему объекты не могут существовать без них?

Здесь аналогия очень простая: класс – это чертёж, максимально подробное описание того, как должно выглядеть изделие. Сам по себе класс не является чем-то физическим и осязаемым, то есть мы не можем использовать его в коде непосредственно. Вместо этого класс является схемой, структурой, на основе которой будет создан объект.

Жизненный цикл объекта

Любая работа с объектами в PHP состоит из следующих этапов.
Начинается всё с создания класса. В классе мы фиксируем из каких свойств и методов будет состоять каждый его экземпляр. Также в классе можно задать начальные значения для каждого свойства.
Имея класс, возможно создать его экземпляр — объект.

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

Чтобы использовать объект в дальнейшем, его следует, как всегда, назначить переменной. Затем вы будете работать с объектом через переменную: вызывать методы и обращаться к свойствам.

Описание класса:

Создание объекта на основе класса:

Разбор примера

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

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

Далее идёт перечисление методов. И начинается всё с метода, у которого особое имя и значение — .

Что такое конструктор объекта

Методы объекта вызываются из внешнего кода, при явном обращении к ним с указанием имени. Но если назвать один метод то он будет вызываться автоматически в момент создания объекта на основе класса.

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

Обращение к свойствам и методам объекта

Посмотрим, как внутри метода происходит обращение к его свойствам.
Во-первых, для этого используется специальная переменная this, которая всегда присутствует внутри объекта и ссылается на него самого.

Во-вторых, для обращения к методам и свойствам объекта нужен специальный синтаксис: «стрелочка». Такая стрелочка отделяет имя свойства или метода от имени объекта. Это аналог квадратных скобок при работе с массивами.

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

Создание объекта на основе класса

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

В коде мы передаём в конструктор почти все параметры погодных наблюдений. Затем для созданного объекта вызываются его методы: первый устанавливает значения осадков, а второй возвращает текстовое описание погоды.

Конструктор класса

Метод конструктора вызывается при создании объекта. Его можно использовать, чтобы все настроить, обеспечить определение необходимых свойств и выполнить всю необходимую предварительную работу. До PHP 5 имя метода конструктора совпадало с именем класса, к которому оно относилось. Так, класс ShopProduct мог использовать метод ShopProduct() в качестве своего конструктора. В PHP 5 вы должны назвать метод конструктора __construct()

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

Давайте определим конструктор для класса ShopProduct:

Код PHP

И снова мы добавляем к классу функциональность, стараясь сэкономить время и силы программиста и избавить его от необходимости дублирования кода, работающего с этим классом. Метод __construct вызывается, когда создается объект с помощью оператора new. Значения всех перечисленных аргументов при создании объекта передаются конструктору. Так, в нашем примере мы передаем конструктору название произведения, имя и фамилию автора, а также цену. В методе конструктора используется псевдопеременная $this для присвоения значений соответствующим свойствам объекта.

В PHP 4 не распознается метод __construct в качестве конструктора. Если вы используете PHP 4, то для создания конструктора объявите метод, имя которого совпадает с именем содержащего его класса. Поэтому для класса с именем ShopProduct можно объявить конструктор с помощью метода под названием ShopProduct(). В PHP по-прежнему поддерживается эта схема именования конструктора. Но если вам не нужна совместимость со старыми версиями PHP то методы конструктора лучше называть __construct.

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

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

Что такое PHP

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

PHP — это препроцессор гипертекста (HTML).
PHP — это серверный язык программирования.
PHP — это скриптовый, интерпретируемый язык программирования.

Зачем нужен PHP

Поясним каждое из определений и узнаем, в каких задачах будет полезен PHP.

Основная задача PHP — это «оживление» HTML страниц.

Обычные HTML-страницы статичны. Статичность (или неизменность) означает, что после того, как страницу создали и загрузили на сайт, при каждом обращении к этой странице браузер покажет её любому пользователю в неизменном виде.

Но этого не всегда достаточно.

Почти всегда пользователи приходят на сайт за информацией, которая всё время меняется, и нужно отображать её актуальное состояние. Например:

  • показать курс валют;
  • подсказать погоду на завтра;
  • вывести счётчик посещений страницы.

Если использовать только HTML, то решить такие задачи не получится. Здесь-то нам и понадобится PHP. Он принимает входящий запрос от веб-сервера, выполняет сценарий и возвращает веб-серверу результат в виде готового HTML-код. Сервер отправляет этот результат в браузер пользователю, который, в свою очередь, отображает её пользователю. После этого видно свежий курс валют, погоду, и что угодно ещё.

РНР позволяет изменять веб-страницу на сервере непосредственно перед тем, как она будет отправлена браузеру. Давайте разберёмся, как это работает. PHP умеет исполнять код — так называемые сценарии. В ходе исполнения PHP может изменить или динамически создать любой HTML-код, который и является результатом исполнения сценария. Затем сервер отправляет этот код браузеру. При этом браузеру не известно, как была сформирована данная страница — статично сверстана верстальщиком, или динамически создана при участии PHP

Это не важно, т.к. браузер всегда работает только с тем, что получил от сервера

Давайте запомним, что сценарий — это программа, которая находится на стороне сервера и запускается в ответ на запрос от браузера.

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

Выполнение сценария также называют его интерпретацией, а сам PHP — интерпретатором.

Вы можете попрактиковаться в создании динамических страниц с помощью PHP в этом тренажёре.

Где используется PHP

Основная сфера применения языка PHP — это веб, то есть сайты, которые мы каждый день посещаем через браузер компьютера или смартфона

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

Практически каждый сайт, который есть в интернете, работает на PHP. Этот язык отлично подходит для любых динамических веб-сайтов, среди которых:

Сериализация объектов

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

  • Передача объектов через поля веб-форм;
  • Передача объектов через адресную строку;
  • Хранение объекта в текстовом файле или одном поле таблицы базы данных.

Для конвертации объекта в строку, и обратно, используются следующие функции:

  • serialize() — принимает объект и возвращает строковое представление его класса и свойств;
  • unserialize() — принимает строку, созданную при помощи serialize(), и возвращает объект.

Давайте посмотрим на эти две функции в деле:

<?php
  
class Member
{
  public $username = "";
  private $loggedIn = false;
  
  public function login() {
    $this->loggedIn = true;
  }
  
  public function logout() {
    $this->loggedIn = false;
  }
  
  public function isLoggedIn() {
    return $this->loggedIn;
  }
}
 
$member = new Member();
$member->username = "Fred";
$member->login();
 
$memberString = serialize( $member );
echo "Converted the Member object to a string: '$memberString'<br>";
echo "Converting the string back to an object...<br>";
$member2 = unserialize( $memberString );
echo $member2->username . " is " . ( $member2->isLoggedIn() ? "logged in" : "logged out" ) . "<br>";
  
?>

Мы создали простенький класс Member с полем public $username, полем private $loggedIn и тремя методами public: login(), logout() и isLoggedIn(). Затем наш скрипт создает объект класса Member, дает ему имя «Fred» и логинит его.

Затем вызываем функцию serialize(), передав ей объект класса Member. serialize() возвращает строковое представление данного объекта, которое мы сохраним в переменной $memberString и отобразим на странице:

Converted the Member object to a string:
'O:6:"Member":2:{s:8:"username";s:4:"Fred";s:16:"MemberloggedIn";b:1;}'

Затем конвертируем нашу строку обратно в объект класса Member, вызвав функцию unserialize(), и сохраняем полученный объект в переменной $member2. Чтобы проверить, что наш объект сконвертировался верно и полностью, мы отображаем значение его поля $username и вызываем его метод isLoggedIn(), чтобы проверить, залогинился ли пользователь. Вот, что отобразится на странице:

Converting the string back to an object...
Fred is logged in

Как видите, строка, созданная функцией serialize(), содержит имя класса, а также названия всех его полей и их значения для конкретного объекта. (Перед полями private записывается имя класса, без пробелов.) Тем не менее, названия методов класса не записываются в строку.

Чтобы сработала функция unserialize(), класс объекта, который должен быть сконвертирован из строки, должен подгружаться еще до того, как идет вызов unserialize(). Вы можете написать сам класс в том же скрипте, где вызывается unserialize(), или подгрузить файл с данным классом через функцию require_once(). Также вы можете создать функцию __autoload(), о которой мы говорили ранее. PHP вызовет __autoload(), если не сможет найти класс, объект которого вы пытаетесь сконвертировать.

На заметку: функции serialize() и unserialize() работают также и с другими типами данных, таких как массивы. Тем не менее, они не работают с ресурсами.

Абстрактные методы

Абстрактные классы также могут
содержать абстрактные методы.

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

А собственно реализация таких методов —
уже задача потомков
.

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

Давайте попробуем на практике.
Пусть предполагается, что все потомки
класса User должны иметь
метод increaseRevenue (увеличить доход).

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

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

Поэтому каждый потомок будет реализовывать
этот метод по-своему.

Фишка тут в том, что абстрактный метод класса User
заставляет программиста реализовывать этот метод
в потомках, иначе PHP выдаст ошибку.

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

Итак, давайте попробуем на практике.
Добавим абстрактный метод increaseRevenue
в класс User:

Пусть наш класс Employee
пока останется без изменений.
В этом случае, даже если не создавать объект
класса Employee, а просто запустить код,
в котором определяются наши классы, — PHP выдаст ошибку.

Давайте проверим — запустите приведенный ниже код:

Давайте теперь напишем реализацию
метода increaseRevenue в классе Employee:

Проверим работу нашего класса:

Реализуем метод increaseRevenue
и в классе Student. Только теперь
наш метод будет увеличивать уже стипендию:

Добавьте в ваш класс User такой же абстрактный
метод increaseRevenue. Напишите реализацию
этого метода в классах Employee и Student.

Добавьте также в ваш класс User абстрактный
метод decreaseRevenue (уменьшить зарплату). Напишите реализацию
этого метода в классах Employee и Student.

5 последних уроков рубрики «PHP»

Когда речь идёт о безопасности веб-сайта, то фраза «фильтруйте всё, экранируйте всё» всегда будет актуальна. Сегодня поговорим о фильтрации данных.

Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак

В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.

Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение

В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.

Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.

Подборка PHP песочниц
Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.

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

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

Adblock
detector