ООП в PHP: Классы, экземпляры класса, поля, методы и конструкторы

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

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

Описание класса и создание его экземпляра

Описание класса в PHP начинается с ключевого слова class. После него следует имя класса. Именовать классы принято с большой буквы. После имени класса в фигурных скобках следует описание членов класса – его полей (данных) и методов.

class ИмяКласса {
  // члены класса
}

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

$экземплярКласса = new ИмяКласса();

Поля класса

При описании полей класса нужно указывать спецификатор доступа – ключевое слово, которое будет определять область видимости поля, к которому оно относится. В php есть три спецификатора доступа: public, protected и private. Спецификатор public обеспечивает доступ к полю из любого места, protected – только из классов стоящих в той же цепочке наследования (из класса-потомка, из потомка потомка и т.д.) и private запрещает доступ ото всюду, кроме самого класса. После спецификатора доступа идёт имя поля, предварённое знаком доллара.

class User {
  public $login;
  public $password;
 
  // описание методов класса
}

В устаревших версиях php не было спецификаторов доступа и для объявления полей класса использовалось ключевое слово var:

class User {
  var $login;
  var $password;
    // описание методов класса
}

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

Для доступа к полям класса используется символ ‘->’. Имя поля, к которому мы хотим обратиться пишется без знака доллара. Поля доступны извне класса в этом случае, потому что мы используем спецификатор доступа public.

$user = new User();
$user->login = 'true-coder';
$user->password = 'qwerty';
 
echo "Логин: $user->login<br />". // Логин: true-coder
  "Пароль: $user->password"; // Пароль: qwerty

Значение полей класса по умолчанию – null:

$user = new User();
echo "Логин: ".getType($user->login)."<br />". // Логин: NULL
  "Пароль: ".getType($user->password); // Пароль: NULL

В php допустимо обращаться к полю, имя которого содержится в строковой переменной:

$user = new User();
 
$propertyName = 'login';
$user->$propertyName = 'true-coder';
$propertyValue = $user->$propertyName;
 
echo "$propertyName: $propertyValue"; // login: true-coder

Такая возможность позволяет в ходе работы скрипта в зависимости от различных условий определять имя поля к которому нужно обратиться.

Значение полей класса по умолчанию можно установить прямо при объявлении любым литеральным (явно указанным) значением. При объявлении нельзя присвоить полю класса результат работы функции, за исключением array(), или экземпляр класса.

class Point {
  public $x = 0;
  public $y = 0;
  // методы
}
 
$p = new Point();
 
echo "x: $p->x<br />". // x: 0
  "y: $p->y"; // y: 0

Обращение к несуществующему полю (свойству) в PHP не вызывает ошибки и, если присвоить такому полю значение, то оно сохранится в экземпляре объекта. Такие поля или свойства называют динамически определяемыми.

$p = new Point();
$p->z = 10;
echo "z: $p->z"; // z: 10

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

На практике такой код, как в классаx User или Point не приемлем. Доступ к полям класса, за редким исключением, необходимо осуществлять посредствам методов, а сами поля делать недоступными за пределами класса или цепочки наследования. Кроме того неудобно присваивать значения полям уже после создания объекта.

Методы класса

Как я говорил ранее, методы описывают поведения экземпляров класса, то есть действия, которые они могут совершать. Описания метода в классе, как и описание поля, начинается со спецификатора доступа, затем следует ключевое слово function, имя метода и список параметров в круглых скобках. Внутри метода доступ к текущему экземпляру класса можно получить при помощи ключевого слова $this, которое, в отличии от многих других языков программирования, в PHP пишется со знаком доллара. За пределами класса вызов методов производится с указанием имени экземпляра класса. Как и для доступа к полям, для вызова методов используется символ ‘->’.

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

class Rectangle {
  public $width;
  public $height;
 
  public function getArea() {
    return $this->width * $this->height;
  }
}

Создадим экземпляр этого класса и вызовем метод getArea:

$rect = new Rectangle();
 
$rect->width = 15;
$rect->height = 20;
 
echo $rect->getArea(); // 15 * 20 = 300

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

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

class Rectangle {
  public $x;
  public $y;
  public $width;
  public $height;
 
  public function getArea() {
    return $this->width * $this->$height;
  }
 
  public function translateTo($x, $y) {
	$this->x = $x;
	$this->y = $y;
  }
}

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

$rect = new Rectangle();
 
$rect->width = 15;
$rect->height = 20;
$rect->x = 10;
$rect->y = 20;
 
echo "Координаты до перемещения: <br />".
  "x: $rect->x<br />". // x: 10
  "y: $rect->y<br />"; // y: 20
 
$rect->translateTo(30, 40);
 
echo "И после: <br />".
  "x: $rect->x<br />". // x: 30
  "y: $rect->y"; // y: 40

Конструкторы

Для того, чтобы присвоить полям значения при создании экземпляра класса существуют конструкторы. Конструктор у класса в php может быть только один и если он не объявлен, то значения экземпляра остаются равными значениям по умолчанию. При объявлении конструктора указывается спецификатор доступа, затем пишется ключевое слово function и __construct. Конструктор, как и метод, имеет доступ ко всём полям класса через ключевое слово $this. Можно рассматривать конструктор как метод, который вызывается при создании экземпляра класса. Напишем конструктор и для нашего класса.

class Rectangle {
  public $x;
  public $y;
  public $width;
  public $height;
 
  public function __construct($x, $y, $width, $height) {
    $this->x = $x;
    $this->y = $y;
    $this->width = $width;
    $this->height = $height;
  }
 
  public function getArea() {
    return $width * $height;
  }
 
  public function translateTo($x, $y) {
    $this->x = $x;
    $this->y = $y;
  }
}

Теперь можно создать прямоугольник вот так:

$rect = new Rectangle(10, 20, 100, 200);
 
echo "x: $rect->x<br />". // x: 10
  "y: $rect->y<br />". // y: 20
  "Ширина: $rect->width<br />". // Ширина: 100
  "Высота: $rect->height<br />"; // Высота: 200

В старых версиях php имя конструктора должно было совпадать с именем класcа, то есть в нашем случае он выглядел бы так:

function Rectangle($x, $y, $width, $height) {
  $this->x = $x;
  $this->y = $y;
  $this->width = $width;
  $this->height = $height;
}

Это устаревший синтаксис и пользоваться им не стоит.

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

Статические поля и методы, константы

В отличие от обычных статические поля одинаковы для всех экземпляров класса и изменение статического свойства приведёт к его изменению для всех экземпляров класса. Обращение к статическим членам внутри класса производится при помощи ключевого слова self, а при их объявлении пишется ключевое слово static. Обращение к статическим членам извне класса производится с указанием имени класса. Для доступа к ним используется символ ‘::’. Статические методы не имеют доступ к обычным (нестатическим) членам, так как в противном случае было бы непонятно к какому экземпляру класса относятся эти нестатические члены, к которым обращается статический метод. В качестве примера можно привести статическое поле, хранящее информацию об количестве экземпляров класса. Сделаем это статическое поле недоступным извне класса при помощи спецификатора доступа private. Узнать количество экземпляров класса будет можно при помощи метода getCount.

class Klass {
  private static $count = 0;
 
  public function __construct() {
    self::$count++;
  }
 
  public static function getCount() {
    return self::$count;
  }
}
 
$instances = array ();
for ($i = 0; $i < 10; $i++) {
  $instances[] = new Klass();
}
 
echo "Количество экземпляров класса: ".Klass::getCount()."<br />"; // 10
$instance = new Klass();
echo "Количество после создание ещё одного объекта: ".Klass::getCount(); // 11

Обратите внимание, что попытка изменить поле count извне класса вызовет ошибку, так как это поле записано со спецификатором доступа private.

//Fatal error: Cannot access private property Klass::$count in …
Klass::$count = -10;

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

class Klass {
  const CONSTANT_NAME = 100500;
 
  public static function printConstant() {
	echo self::CONSTANT_NAME;
  }
}
 
Klass::printConstant(); // 100500

Константа так же доступна извне класса:

echo Klass::CONSTANT_NAME; // 100500

Пару слов об оформлении кода

Чтобы ваш код легко и привычно могли читать другие разработчики, стоит немного позаботиться о его оформлении.
Каждый класс обычно хранят в отдельном файле, который содержит названия класса. Так же иногда принято через точку добавлять слово class. Например: user.class.php, post.class.php, connection.class.php.

При написании класса желательно придерживаться следующий схемы: вначале пишутся поля класса, затем конструктор, затем публичные методы, затем приватные. Публичные методы располагают выше, поскольку при помощи их описания разработчик понимает, как класс взаимодействует с кодом извне.
Объявление полей отделяют от конструктора пустой строкой, ими же отделяются друг от друга методы в классе. Так же пустую строку можно использовать, чтобы отделать объявление констант, статических полей и обычных полей друг от друга.
Имя класса должно быть существительным. Использование прилагательных говорит о том, что класс неправильно спроектирован. За свойства сущности, которую описывает класс, должны отвечать свойства (поля). Использование глаголов в имени класса так же неверно. За поведения экземпляра класса отвечают методы.
Методы, как вы уже поняли должны именоваться глаголами или глагольными сочетаниями, поскольку они ассоциируются с действиями, которые может совершать моделируемая сущность.
Существуют два распространенных способа написания идентификаторов: СamelСase (ВерблюжийРегистр) и отделение каждого слова в идентификаторе нижним подчёркиванием. Имена в camel case будут выглядеть вот так: MyClassName, myVariableName, methodName. Хотя можно использовать и второй стиль: My_class_name, my_variable_name, method_name.

В php сложность ещё состоит в том, что нативные функции, классы, методы и переменные именованы в разных стилях. Сравните: DateTime, in_array, $_POST, $GLOBALS. Поэтому сделать код идеально единообразным у вас не получится, но свои собственные функции, классы, методы и переменные именуйте в одном стиле.

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

И, как всегда, успехов вам!

  • Dmitry

    Спасибо за интересную статью.

  • Пожалуйста, Dmitry. Рад, что вам понравилось.

  • Max

    А что тут используется для подсветки кода? Какой плагин? Или же вручную код красили?

  • Доходчиво написано, буду дальше читать ваши статьи. Спасибо.

  • Роман

    Спасибо большое, очень все доходчиво и понятно.
    Не могу понять только одно:
    -вот есть конструктор
    public function __construct($x, $y, $z){
    $this->x =$x; //тут понятно

    }
    тоесть те значения, которые предопределены в __construct уже не нужно
    определять в функциях(методах)
    Например:
    public function coolMix($x, $y){
    $this->x = $x; //тут уже не нужно, да?
    $this->y = $y //тут уже не нужно, да?
    }

    • Нет, повторно определять в методах поля, которые были инициализированы в конструкторе, нет необходимости.

  • Кирилл

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

  • dezzzus

    Изменится ли что-нибудь если я буду создавать экземпляр без скобок, например: $engine = new Engine; ?