[Webshake] Курс «PHP для начинающих» - Часть 2

Изучаем массивы в PHP

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

Давайте теперь создадим в папке с нашим проектом файл для наших экспериментов с массивами. Назовём его arrays.php .

Объявляется массив в PHP с помощью квадратных скобок, вот так:

<?php
$fruits = [];

Вот так мы создали пустой массив.

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

$fruits = array();

Сейчас такая запись является устаревшей, использовать её не стоит!

Можно также создать массив, в котором уже будут какие-либо значения. Это делается так:

$fruits = ['apple', 'orange', 'grape'];

Вывести получившийся массив можно с помощью уже известной нам функции var_dump:

<?php

$fruits = ['apple', 'orange', 'grape'];
var_dump($fruits);

И давайте запустим этот наш скрипт, открыв в браузере: http://myproject.loc/arrays.php
Мы увидим следующее:

C:\OpenServer\domains\myproject.loc\www\arrays.php:4:
array (size=3)
  0 => string 'apple' (length=5)
  1 => string 'orange' (length=6)
  2 => string 'grape' (length=5)

Нумерованные массивы

Array – это тип объекта, а именно массив. Size=3 – это размерность массива (содержит 3 элемента). 0, 1, 2 – это ключи массива , которые также называются индексами . По этим ключам хранятся значения, в нашем случае эти значения – строки. Как мы можем видеть, ключи массива нумеруются с нуля, и далее просто увеличиваются на единицу.

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

Например, чтобы получить нулевой элемент массива (с ключом 0), мы должны сделать следующее:

<?php

$fruits = ['apple', 'orange', 'grape'];

echo $fruits[0];

Результат этого кода:

apple

Давайте получим элемент с индексом 2:

<?php

$fruits = ['apple', 'orange', 'grape'];

echo $fruits[2];

Результат:

grape

Если же мы попытаемся получить элемент с несуществующим индексом, например – 3:

<?php

$fruits = ['apple', 'orange', 'grape'];

echo $fruits[3];

То мы получим warning о том, что элемент с таким ключом не найден.
Ошибка при получении несуществующего элемента массива

Добавление и удаление элементов массива

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

$fruits[] = 'mango';

Давайте после этого снова посмотрим на наш массив с помощью var_dump:

<?php

$fruits = ['apple', 'orange', 'grape'];

$fruits[] = 'mango';

var_dump($fruits);

Результат – ещё один элемент с индексом 3:

C:\OpenServer\domains\myproject.loc\www\arrays.php:7:
array (size=4)
  0 => string 'apple' (length=5)
  1 => string 'orange' (length=6)
  2 => string 'grape' (length=5)
  3 => string 'mango' (length=5)

Для удаления элементов массива используется конструкция unset. Давайте удалим элемент с индексом 2:

<?php

$fruits = ['apple', 'orange', 'grape'];

$fruits[] = 'mango';

unset($fruits[2]);

var_dump($fruits);

Результат этого кода:

C:\OpenServer\domains\myproject.loc\www\arrays.php:9:
array (size=3)
  0 => string 'apple' (length=5)
  1 => string 'orange' (length=6)
  3 => string 'mango' (length=5)

Как мы видим, элемента с индексом 2 больше нет, и образовалась «дырка» в порядковых номерах ключей. Если теперь добавить новый элемент, то у него индекс будет равен 4, но эта дыра по-прежнему останется. Это работает именно так и об этом нужно помнить.

И снова вернёмся к ключам

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

<?php

$fruits = [5 => 'apple', 3 => 'orange', 9 => 'grape'];

var_dump($fruits);

Результат:

C:\OpenServer\domains\myproject.loc\www\arrays.php:5:
array (size=3)
  5 => string 'apple' (length=5)
  3 => string 'orange' (length=6)
  9 => string 'grape' (length=5)

Как мы видим, ключи теперь – это 5, 3 и 9.
Если теперь добавить элемент в массив, то у него будет индекс, на единицу больше максимального числового значения ключа:

<?php

$fruits = [5 => 'apple', 3 => 'orange', 9 => 'grape'];

$fruits[] = 'mango';

var_dump($fruits);

Результат:

C:\OpenServer\domains\myproject.loc\www\arrays.php:7:
array (size=4)
  5 => string 'apple' (length=5)
  3 => string 'orange' (length=6)
  9 => string 'grape' (length=5)
  10 => string 'mango' (length=5)

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

<?php

$fruits = [5 => 'apple', 3 => 'orange', 9 => 'grape'];

$fruits[20] = 'mango';

var_dump($fruits);

Результат:

C:\OpenServer\domains\myproject.loc\www\arrays.php:7:
array (size=4)
  5 => string 'apple' (length=5)
  3 => string 'orange' (length=6)
  9 => string 'grape' (length=5)
  20 => string 'mango' (length=5)

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

<?php

$fruits = [5 => 'apple', 3 => 'orange', 9 => 'grape'];

var_dump($fruits);

$fruits[5] = 'mango';

var_dump($fruits);

Результат:

C:\OpenServer\domains\myproject.loc\www\arrays.php:5:
array (size=3)
  5 => string 'apple' (length=5)
  3 => string 'orange' (length=6)
  9 => string 'grape' (length=5)

C:\OpenServer\domains\myproject.loc\www\arrays.php:9:
array (size=3)
  5 => string 'mango' (length=5)
  3 => string 'orange' (length=6)
  9 => string 'grape' (length=5)

Ассоциативные массивы

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

<?php

$article = ['title' => 'Название статьи', 'text' => 'Текст статьи'];
$article['author'] = 'Имя автора';

var_dump($article);

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

C:\OpenServer\domains\myproject.loc\www\arrays.php:6:
array (size=3)
  'title' => string 'Название статьи' (length=29)
  'text' => string 'Текст статьи' (length=23)
  'author' => string 'Имя автора' (length=19)

Отлично, а теперь мы можем использовать это внутри HTML-разметки:

<?php
$article = [
    'title' => 'Название статьи',
    'text' => 'Текст статьи',
    'author' => 'Автор статьи'
];
?>
<html>
<head>
    <title><?= $article['title'] ?></title>
</head>
<body>
<h1><?= $article['title'] ?></h1>
<p><?= $article['text'] ?></p>
<p><?= $article['author'] ?></p>
</body>
</html>

Результат:
Вывод информации из массива

Многомерные массивы

Помните, в начале урока я сказал, что по ключам массива хранятся какие-то значения, и в нашем случае это строки. Так вот, элементом массива, вообще говоря, может являться что угодно. Даже сам массив. =)

Давайте на примере нашей статьи – у автора может быть имя и фамилия. И мы хотим хранить их отдельно друг от друга. Тогда автор – это массив с двумя ключами – first_name и last_name.
Давайте сделаем это:

<?php
$article = [
    'title' => 'Название статьи',
    'text' => 'Текст статьи',
    'author' => [
        'first_name' => 'Иван',
        'last_name' => 'Иванов'
    ]
];
var_dump($article);

Результат:

C:\OpenServer\domains\myproject.loc\www\arrays.php:10:
array (size=3)
  'title' => string 'Название статьи' (length=29)
  'text' => string 'Текст статьи' (length=23)
  'author' => 
    array (size=2)
      'first_name' => string 'Иван' (length=8)
      'last_name' => string 'Иванов' (length=12)

Вот так всё просто, article – это массив, у которого по ключу author – тоже находится массив.
Чтобы получить имя автора нужно использовать следующий код:

<?php
$article = [
    'title' => 'Название статьи',
    'text' => 'Текст статьи',
    'author' => [
        'first_name' => 'Иван',
        'last_name' => 'Иванов'
    ]
];
echo $article['author']['first_name'];

Сначала в массиве $article получили значение по ключу author, этим значением оказался массив. А затем уже из этого массива получили значение по ключу first_name. И результат этого кода, разумеется:

Иван

Давайте теперь используем эти значения в уже использованном нами шаблоне:

<?php
$article = [
    'title' => 'Название статьи',
    'text' => 'Текст статьи',
    'author' => [
        'first_name' => 'Иван',
        'last_name' => 'Иванов'
    ]
];
?>
<html>
<head>
    <title><?= $article['title'] ?></title>
</head>
<body>
<h1><?= $article['title'] ?></h1>
<p><?= $article['text'] ?></p>
<p><?= $article['author']['first_name'] . ' ' . $article['author']['last_name'] ?></p>
</body>
</html>

Результат:
Вывод ассоциативного массива

Разумеется, можно и внутри этого значения создать массив, а внутри него – ещё один, пока не надоест.

В следующем уроке мы рассмотрим более сложные и интересные примеры работы с массивами.

Домашнее задание

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

Цикл foreach в PHP

Всем привет! В прошлом уроке мы с вами изучили основы массивов в PHP. Как вы уже поняли, массив может иметь довольно много элементов, и каждый из этих элементов, в свою очередь, также может являться массивом. Само собой, напрашивается вопрос: «А можно ли как-то автоматизировать обход по массиву?». То есть обращаться к его элементам по одному, не указывая при этом индексы значений. Ответ – можно. Для этого в PHP есть такая замечательная вещь, как циклы.

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

$carsSpeeds = [
    95,
    140,
    78
];

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

$averageSpeed = ($carsSpeeds[0] + $carsSpeeds[1] + $carsSpeeds[2]) / 3;

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

Рассмотрим в первую очередь языковую конструкцию foreach (от английского for – для, each - каждый). Она позволяет пройтись по каждому элементу массива. Она просто проходит по каждому элементу массива по очереди и позволяет выполнить с ним какое-либо действие.
Использование конструкции выглядит следующим образом:

foreach ($array as $index => $value) {
    ...
}

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

То, что находится внутри фигурных скобок будет повторяться для каждого элемента массива. Один такой проход называется шагом или итерацией . Один элемент = одна итерация. Запомните это слово.
Цикл будет идти по массиву, пока не закончатся все элементы. Затем программа продолжит своё выполнение.

Давайте в качестве примера со скоростями обойдём массив и просто выведем индексы массива и его значения.

<?php

$carsSpeeds = [
    95,
    140,
    78
];

foreach ($carsSpeeds as $index => $speed) {
    echo $index . ' ' . $speed . '<br>';
}

Результат:

0 95
1 140
2 78

Как мы видим, на первом шаге цикла в переменные $index и $speed попали значения 0 и 95 соответственно. На втором – 1 и 140 и так далее. Всё как мы и ожидали. Было выполнено 3 итерации.

Если нам не нужны индексы массива, то их можно не указывать в этой конструкции:

foreach ($array as $value) {
    ...
}

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

Вернёмся к задаче

Итак, нам нужно найти среднюю скорость. Для этого нам нужно:

  1. сложить все значения скоростей
  2. поделить получившуюся сумму на кол-во значений

Для того, чтобы выполнить первый шаг, давайте прибегнем к помощи цикла foreach:

<?php

$carsSpeeds = [
    95,
    140,
    78
];

$sumOfSpeeds = 0;

foreach ($carsSpeeds as $speed) {
    $sumOfSpeeds += $speed;
}

echo $sumOfSpeeds;

Перед началом цикла определили переменную $sumOfSpeeds равную 0, а затем на каждой итерации прибавляли к ней значения скоростей. В конце просто вывели получившийся результат, который равен 313. Можете проверить на калькуляторе.

Теперь нужно разделить получившееся значение на число элементов.

Чтобы узнать число элементов в массиве можно прибегнуть к функции count($carSpeeds) – на вход передаётся имя массива. Полностью наш код будет выглядеть следующим образом:

<?php

$carsSpeeds = [
    95,
    140,
    78
];

$sumOfSpeeds = 0;

foreach ($carsSpeeds as $speed) {
    $sumOfSpeeds += $speed;
}

$countOfCars = count($carsSpeeds);

$averageSpeed = $sumOfSpeeds / $countOfCars;

echo 'Средняя скорость движения по трассе: ' . $averageSpeed;

На этом всё. Всем пока!

Домашнее задание

  • Придумайте способ обойтись без использования функции count.

While: самый простой цикл в PHP

В этом уроке мы поговорим о ещё одном цикле в PHP – цикле while. Этот цикл является самым простым из всех. Его структура выглядит следующим образом:

<?php

while (условие) {
    какие-то действия;
}

Если результат выражения в круглых скобках – true, будут выполнены действия в фигурных скобках. После этого снова будет произведена проверка выражения на истинность. Цикл будет прекращен, когда результат выражения окажется равным false. While в переводе с английского означает «до тех пор пока». Так и получается, цикл будет выполняться до тех пор, пока условие выполняется. Давайте рассмотрим несколько простых примеров, когда цикл while будет нам полезен.

Задача #1 – вывести числа от 0 до 10

Решение с использованием цикла while:

<?php

$i = 0;

while ($i <= 10) {
    echo $i++;
    echo '<br>';
}

Сначала определили переменную $i = 0. Затем в условии цикла while говорим, что нужно выполнять его, пока $i меньше либо равно 10. На каждой итерации мы выводим переменную $i и после вывода увеличиваем её на единицу. Выводим тег переноса строки и снова переходим к условию. И так до тех пор, пока $i не станет равной 11. В этот момент условие не выполнится и цикл завершится.

Задача #2 – вывести степени числа 2, меньшие 100000

Решение:

<?php

$i = 2;

while ($i < 100000) {
    echo $i;
    $i *= 2;
    echo '<br>';
}

Результат:

2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536

Пояснять не буду, всё довольно просто.

Домашнее задание

  • С помощью цикла while создайте массив, содержащий чётные числа от 345 до 357. Затем выведите элементы массива с помощью цикла foreach.
  • Запустите следующий код:
    <?php
    while (true) {
    echo 1;
    }
  • К чему это привело?
  • Изучите, для чего нужна директива max_execution_time в файле конфигурации php.ini и установите её равной одной секунде (этот файл мы с вами уже редактировали в этом уроке, как это делать смотрите там же).
  • Снова запустите этот код.

Цикл for: работаем с числами

Всем привет! В этом уроке поговорим об еще одном цикле в PHP – for. Он предназначен для случаев, когда нужно работать с числовыми индексами массивов, или же когда нужно осуществлять перебор чисел. Он поначалу кажется более сложным, чем foreach или while, но на самом деле ничего сложного нет.

Выглядит цикл следующим образом:

<?php

for (expr1; expr2; expr3) {
    //loop body
}

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

  • expr1 – это выражение выполняется только один раз перед началом цикла. Здесь обычно происходит определение переменной.
  • expr2 – здесь задаётся условие. Если результат этого выражения – true, то цикл выполнится, иначе – закончится. Например, здесь можно проверить, что переменная меньше какого-либо значения.
  • expr3 – тут указывается действие, которое будет выполняться в конце каждой итерации цикла. Например – переменная увеличивается на единицу.

Пример можно? Да пожалуйста!

Давайте теперь рассмотрим реальный пример:

<?php

for ($i = 0; $i < 100; $i++) {
    echo $i;
    echo '<br>';
}

Данный код выведет все числа от 0 до 99.

  1. Итак, перед началом цикла мы определяем переменную $i = 0.
  2. Затем, перед тем как выполнить цикл, мы проверяем, что переменная $i меньше 100. Если это так, то выполнится тело цикла. А именно – выведется текущее значение переменной и выполнится перенос строки.
  3. Следующим шагом будет выполнение третьего выражения – инкремент переменной $i.
  4. Теперь снова будет выполнена проверка условия, что $i < 100. И так далее, пока условие не перестанет выполняться. А если быть точнее – пока $i не станет равной 100. Тут цикл завершит работу.

Есть ещё что-то, что нужно знать?

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

<?php

$i = 0;
for (; $i < 100; $i++) {
    echo $i;
    echo '<br>';
}

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

Можно убрать и третье выражение, которое выполняется после итерации. Перенесем инкремент внутрь тела цикла и результат получится тот же:

<?php

$i = 0;
for (; $i < 100;) {
    echo $i++;
    echo '<br>';
}

Более того, могут вообще отсутствовать все аргументы. При отсутствии второго аргумента получится бесконечный цикл:

<?php

for (;;) {
    //этот код будет выполняться бесконечно
}

Где это применимо?

Цикл for можно применять там, где требуется перебор чисел, или, например, выполнение определенного числа шагов.

Давайте рассмотрим несколько примеров.

Найти все чётные числа от 1 до 50 и вывести их

<?php

for ($i = 1;$i <= 50;$i++) {
    if ($i % 2 === 0) {
        echo $i;
        echo ' ';
    }
}

Создать массив из 50 случайных значений

<?php

$randomValues = [];

for ($i = 1;$i <= 50;$i++) {
    $randomValues[] = mt_rand();
}

var_dump($randomValues);

Посчитать сумму чисел от 1 до 1000

<?php

$sum = 0;
for ($i = 1;$i <= 1000;$i++) {
    $sum += $i;
}

echo $sum;

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

Домашнее задание

  • Найдите числа от 1000 до 1000000, которые делятся на 17 без остатка.
  • Найдите числа Фибоначчи меньше 100000. Числа Фибоначчи – это последовательность чисел, в которой первые два числа равны 0 и 1, а каждое последующее число равно сумме двух предыдущих чисел.
    Должно получиться: 0, 1, 1, 2, 3, 5, 8 …

Операторы break и continue в PHP

Очень часто при работе с циклами требуется пропустить итерацию из-за каких-то условий, и перейти к следующей. Кроме того, порой и вовсе нужно прервать цикл ещё до того, как он должен был завершиться. Для этого используются специальные операторы – continue (для перехода к следующей итерации) и break (остановка цикла).

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

Пример использования break

Давайте напишем небольшой скрипт, который будет искать число в массиве. Для начала определим массив и число:

<?php

$array = [2, 3, 6, 1, 23, 2, 56, 7, 1, 15];
$number = 1;

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

<?php

$array = [2, 3, 6, 1, 23, 2, 56, 7, 1, 15];
$number = 1;

$isNumberFound = false;
foreach ($array as $item) {
    echo 'Сравниваем с числом элемент ' . $item . '<br>';
    if ($item === $number) {
        $isNumberFound = true;
    }
}

echo $isNumberFound ? 'Число найдено' : 'Число не найдено';

Перед циклом мы создали переменную $isNumberFound , которая будет хранить информацию о том, найдено ли число в массиве или нет. Изначально она равна false .

Затем мы начинаем идти по массиву и сравнивать каждый его элемент с числом. Если совпадение найдено, то значение переменной $isNumberFound становится равной true, и теперь мы уже знаем, что искомое число в массиве есть.

При этом на каждой итерации перед проверкой мы выводим информацию о том, какое сравнение сейчас производится.

Выполнение скрипта

При этом мы видим, что все элементы массива сравнивались с числом. И мы понимаем, что нам было бы достаточно найти число и на этом завершить работу цикла. С помощью оператора break это сделать проще простого!

<?php

$array = [2, 3, 6, 1, 23, 2, 56, 7, 1, 15];
$number = 1;

$isNumberFound = false;
foreach ($array as $item) {
    echo 'Сравниваем с числом элемент ' . $item . '<br>';
    if ($item === $number) {
        $isNumberFound = true;
        break;
    }
}

echo $isNumberFound ? 'Число найдено' : 'Число не найдено';

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

Ход выполнения программы после оптимизации

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

Пример использования continue

Не менее редко при каких-то условиях требуется перейти к следующей итерации, не доходя до конца текущей. К примеру – мы хотели бы вывести все числа от 1 до 20, за исключением тех, что делятся на 3 без остатка.

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

<?php

for ($i = 1; $i <= 20; $i++) {
    if ($i % 3 != 0) {
        echo $i;
        echo ' ';
    }
}

Результат будет следующим:
Числа которые не делятся на 3

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

Выглядеть это будет так.

<?php

for ($i = 1; $i <= 20; $i++) {
    if ($i % 3 === 0) {
        continue;
    }

    echo $i;
    echo ' ';
}

Результат работы этого кода будет таким же, как и в предыдущем случае. Но с точки зрения сложности чтения и понимания, код упростился. Я понимаю, сейчас трудно уловить эту тонкую грань, но подумайте вот о чём. В задании мы говорили о том, чтобы вывести числа, не делящиеся на 3 без остатка. Значит эти числа – первичны. А остальные числа, которые делятся на 3, нам не нужны. Значит они для нас должны иметь второстепенное значение. И в цикле мы просто отсекаем лишнее, а затем идёт основной алгоритм – вывод того, что нам нужно.

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

А сейчас – за домашнее задание.

Домашнее задание

  • напишите функцию , принимающую на вход 2 аргумента - массив и какое-либо значение. Функция возвращает true, если переданное значение присутствует в массиве и false - если нет.
  • напишите функцию , принимающую на вход 2 аргумента - массив и какое-либо значение. Функция возвращает число вхождений числа в массив. Например: для массива [1, 2, 1, 3] число вхождений числа “1” будет равно двум.

Изучаем функции для работы с массивами

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

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

Проверка существования ключа в массиве

Зачастую мы не знаем, есть ли в массиве какой-либо ключ. И если мы попробуем обратиться по ключу, которого в массиве нет – возникнет ошибка. Простейший пример.

<?php

$array = [
    'login' => 'admin'
];

echo $array['password'];

В результате, как и ожидали – ошибка.

Ошибка при обращении к несуществующему элементу массива

Чтобы подобной ситуации избежать, следует проверять, есть ли такой ключ в массиве или нет. Для этого есть функция array_key_exists($key, $array). Она принимает на вход первым аргументом ключ, а вторым – массив, в котором его наличие нужно проверить. Возвращает true, если ключ найден, и false – если нет. Использование выглядит следующим образом.

<?php

$array = [
    'login' => 'admin'
];

if (array_key_exists('password', $array)) {
    echo $array['password'];
} else {
    echo 'Ключ "password" в массиве не найден';
}

Результат:
Ошибки не возникло

Проверка существования значения

Не менее часто приходится узнать, а есть ли значение в массиве. Например, мы хотим узнать, есть ли число 7 в массиве. Как мы помним, в прошлом уроке мы самостоятельно написали для этого функцию. Однако в реальном кодинге нужно по большей части пользоваться готовым. Эта функция называется in_array($needle, $array). Здесь $needle – это то, что ищется, $array – массив, в котором ищем. Она возвращает true, если значение найдено, и false – если нет. Выглядит её применение следующим образом.

<?php

$numbers = [
    1,
    0,
    7,
    4
];

if (in_array(7, $numbers)) {
    echo 'В массиве есть число 7.';
}

Объединение массивов

Очень часто требуется объединить 2 массива в один. Для этого есть функция array_merge($array1, $array2). В качестве аргументов, как вы поняли, этой функции передаются массивы, которые нужно объединить. Возвращает эта функция получившийся массив.

<?php

$articlesFromIvan = [
    'Статья 1',
    'Статья 2'
];

$articlesFromMaria = [
    'Статья 3',
    'Статья 4'
];

$allArticles = array_merge($articlesFromIvan, $articlesFromMaria);

var_dump($allArticles);

Результат:
Объединение двух массивов

Обратите внимание, что если ключи будут строковыми, то поведение функции будет несколько иным. Если строковые ключи из $array2 будут пересекаться с ключами из массива $array1, то в результат для пересекающихся ключей попадут только значения из $array2. Они как бы перезапишут то, что было в первом массиве.

<?php

$array1 = [
    'login' => 'admin',
    'password' => '123'
];

$array2 = [
    'password' => '666'
];

var_dump(
    array_merge($array1, $array2)
);

Результат получится следующим:
Объединение массивов со строковыми ключами

Это может быть удобно, когда нам необходимо обновить часть каких-то данных в массиве. Как в примере выше – мы обновили пароль, оставив логин прежним.

Другие функции

Функций для работы с массивами в PHP очень много. И остальные вы найдёте на сайте с документацией PHP.
А домашнее задание вам в этом поможет.

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

Домашнее задание

  1. Есть массив чисел – [1, 3, 2]. Отсортируйте их от меньшего к большему и преобразуйте в строку, в которой значения элементов массива разделяются двоеточиями. В результате должна получиться строка “1:2:3”.
  2. Есть массив чисел – [1, 2, 3, 4, 5]. Получите с помощью одной функции массив, в котором будут элементы исходного с 1-го элемента по 3-й. В результате должен получиться массив с числами [2, 3, 4].
  3. Есть массив чисел – [1, 2, 5, 2, 3, 1, 6]. Получите такой массив, в котором каждое значение из исходного будет содержаться только один раз. В результате должен получиться массив, содержащий числа [1, 2, 5, 3, 6].

GET-запросы в PHP

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

Итак, давайте удалим все наши файлы в проекте и создадим новый пустой файл с именем index.php. Запишем в него следующий код:

<?php

echo 'Hello world!';

Давайте выполним этот скрипт, открыв в браузере адрес:
http://myproject.loc/index.php

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

Возникает вопрос: «А как можно передать от пользователя внутрь скрипта какие-либо данные?». Ответов на него у нас сразу несколько, и все мы их рассмотрим в этом уроке.

Метод GET

Когда мы вбиваем адрес скрипта и нажимаем Enter, выполняется GET-запрос по протоколу HTTP. В этом типе запроса к основному адресу мы можем прикрепить дополнительные параметры. Для того чтобы передать их, в конце основного адреса ставится знак вопроса, и мы можем перечислять эти параметры — вот так:
параметр1=значение1.
При этом если нам нужно указать несколько параметров, то мы разделяем их с помощью знака амперсанда:
арг1=знач1&арг2=знач2.

Пример:
http://myproject.loc/index.php?arg1=123&arg2=scrrr

Если сейчас перейти по этой ссылке в браузере, то на сервер передадутся 2 параметра:

  • arg1 со значением 123;
  • arg2 со значением scrrr.

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

Давайте изменим код нашего index.php, чтобы узнать, что именно хранится в этой переменной.

<?php

var_dump($_GET);

И откроем этот url: http://myproject.loc/index.php?arg1=123&arg2=scrrr

Мы увидим следующее:

array (size=2)
  'arg1' => string '123' (length=3)
  'arg2' => string 'scrrr' (length=5)

Как мы можем видеть, это действительно наши переданные аргументы, представленные в виде ассоциативного массива.

Учимся обрабатывать параметры

Давайте попробуем передать другие аргументы:
http://myproject.loc/index.php?login=admin&password=12345

Результат:

array (size=2)
  'login' => string 'admin' (length=5)
  'password' => string '12345' (length=5)

Разумеется, мы можем обращаться к этим элементам как к элементам обычного массива. Например, так:

<?php
echo $_GET['login'] 

Давайте создадим простую страничку, на которой мы будем выводить переданные с помощью GET-запроса логин и пароль.

<?php
$login = !empty($_GET['login']) ? $_GET['login'] : 'логин не передан!';
$password = !empty($_GET['password']) ? $_GET['password'] : 'пароль не передан!';
?>
<html>
<head>
    <title>Знакомство с GET-запросами</title>
</head>
<body>
<p>
    Переданный логин: <?= $login ?>
    <br>
    Переданный пароль: <?= $password ?>
</p>
</body>
</html>

Обновим нашу страничку в браузере и увидим результат.
Обработанные query-параметры

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

Учимся работать с формами

Давайте теперь вспомним уроки про формы из курса по HTML и сделаем простейшую форму для отправки GET-запроса на страничку.

Давайте запишем в наш index.php следующий код:

<html>
<head>
    <title>Форма входа</title>
</head>
<body>
<form action="/login.php" method="get">
    <label>
        Логин <input type="text" name="login">
    </label>
    <br>
    <label>
        Пароль <input type="password" name="password">
    </label>
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

И давайте теперь откроем его в браузере: http://myproject.loc/index.php

Как мы видим по исходному коду, форма отправит аргументы login и password с введенными значениями на адрес /login.php.

Давайте введем в поля значения admin и Pa$$w0rd соответственно и нажмем на кнопку «Войти».
Форма входа

Нас отправит на страничку http://myproject.loc/login.php?login=admin&password=Pa%24%24w0rd

Где сервер нам скажет о том, что такой странички не найдено.
Ошибка 404

Отлично! Так давайте же её создадим! Создаём рядом с нашим index.php файл с именем login.php. И пишем в него следующий код:

<?php
$login = !empty($_GET['login']) ? $_GET['login'] : '';
$password = !empty($_GET['password']) ? $_GET['password'] : '';

if ($login === 'admin' && $password === 'Pa$w0rd') {
    $isAuthorized = true;
} else {
    $isAuthorized = false;
}
?>
<html>
<head>
    <title>Результат авторизации</title>
</head>
<body>
<p>
    <?= $isAuthorized ? 'Логин и пароль верные!' : 'Неправильный логин или пароль' ?>
</p>
</body>
</html>

Вернёмся на нашу форму и повторно отправим пароль. Теперь мы увидим информацию о том, что мы успешно авторизовались. Это простейший прототип формы авторизации. Разумеется, он сильно упрощён, не всё сразу. А пока – делаем домашнее задание.

Домашнее задание

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

Добавьте дополнительное условие, которое будет говорить о том, что пользователь не найден, если переданный логин не ‘admin’. И если пользователь не найден, то нет смысла проверять пароль, и это условие проверяться не будет. Если же логин ‘admin’, но пароль не совпадает, то писать о том, что пароль неверный.

Обрабатываем POST-запросы в PHP

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

В предыдущем уроке мы научились работать с GET-запросами. Как мы помним, при этом способе данные передаются на сервер с помощью параметров в адресной строке. Такое бывает удобно, когда нам нужно поделиться ссылкой в которой эти параметры нужны. Например, у нас спрашивают, где в Москве купить шкаф. И мы скидываем человеку ссылку на поиск в Яндексе:
https://yandex.ru/search/?text=шкаф%20в%20москве%20купить

Он переходит по ней и прекрасно себя чувствует, потому что всё уже введено за него.

Однако, параметры в адресной строке – это не всегда уместно. Например, когда в параметрах содержится какая-то конфиденциальная информация: пароль, пин-код. И любой мимо проходящий человек может её увидеть. Как в такой ситуации быть? Использовать POST-запросы!

Что это такое? Да всё тот же запрос от клиента к серверу, только параметры передаются внутри тела запроса, а не в адресной строке. И увидеть их просто так не получится.

Что за тело запроса? Ну, это просто данные, которые передаются на сервер. При этом они скрыты от лишних глаз.

Чтобы отправить POST-запрос нужно в HTML-форме задать для атрибута method значение POST.

Вот так:

<html>
<head>
    <title>Форма входа</title>
</head>
<body>
<form action="/login.php" method="post">
    <label>
        Логин <input type="text" name="login">
    </label>
    <br>
    <label>
        Пароль <input type="password" name="password">
    </label>
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

Данные, отправленные с помощью POST-запроса доступны в PHP в суперглобальном массиве $_POST .

Давайте выведем переданные скриптом выше значения login и password. Для этого в файл login.php положим следующий код:

<?php
$login = !empty($_POST['login']) ? $_POST['login'] : '';
$password = !empty($_POST['password']) ? $_POST['password'] : '';
?>
<html>
<head>
    <title>Обработка POST-запроса</title>
</head>
<body>
<p>
    Переданный login: <?= $login ?>
    <br>
    Переданный password: <?= $password ?>
</p>
</body>
</html>

Откройте теперь форму, введите в неё значения и нажмите кнопку «Войти».
Вы увидите введенные вами в форме данные, при этом они будут отсутствовать в адресной строке. Вуаля! Теперь никто не подсмотрит ваш пароль в адресной строке.
Результат POST-запроса

Как увидеть тело POST-запроса

Чтобы увидеть данные POST-запроса в браузере, в Google Chrome нужно перейти в режим разработчика. Нажмите клавишу F12, находясь в окне браузера. После этого вы увидите панель разработчика.
Панель разработчика в Google Chrome

Перейдите во вкладку Network, а затем установите галочку напротив пункта Preserve log.

Теперь вернитесь на форму, и снова введите данные, после чего нажмите на кнопку «Войти».
Форма отправки POST-запроса

Одновременно с тем, как вы нажмете на кнопку входа, вы увидите в панели разработчика запрос на login.php.
POST-запрос в списке

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

Нас здесь интересует вкладка Headers. Перейдите в неё, и прокрутите содержимое в самый низ. Здесь вы увидите те данные, что браузер отправил на сервер.
Тело POST-запроса

Заключение

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

А сейчас - немного нестандартное домашнее задание =)

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

Домашнее задание

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

Пишем калькулятор на PHP

Всем привет! Мы с вами изучили 2 типа запросов: GET и POST. Они позволяют нам отправлять данные на сервер, благодаря чему мы можем с ним «общаться». Мы рассмотрели несколько простейших примеров. В этом уроке для закрепления материала мы с вами напишем свой калькулятор!

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

Итак, вот так будет выглядеть форма для ввода исходных данных:
Форма калькулятора

А вот так будет выглядеть страница с результатом:
Результат вычислений

Давайте теперь спроектируем, где что будет лежать. Я предлагаю сделать такую архитектуру:

  • index.php – здесь будет храниться форма, в которой мы будем заполнять исходные данные
  • result.php – здесь будет храниться шаблон, который будет выводить результат вычислений
  • calc.php – файл, в котором будет храниться непосредственно вся бизнес-логика нашего приложения.

Шаблоны калькулятора

Итак, приступим. Давайте начнём с формы. Она будет содержать в себе:

  • 2 input’а, в которые мы будем записывать аргументы;
  • select, который позволит нам выбрать одну из доступных операций;
  • кнопку, для отправки формы.

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

<html>
<head>
    <title>Калькулятор</title>
</head>
<body>
<form action="/result.php">
    <input type="text" name="x1">
    <select name="operation">
        <option value="+">+</option>
        <option value="-">-</option>
    </select>
    <input type="text" name="x2">
    <input type="submit" value="Посчитать">
</form>
</body>
</html>

Здесь вам всё должно быть знакомо. Если нет — повторите уроки с формами в курсе HTML.

Мы видим, что данная форма отправляет GET-запрос на адрес /result.php. Как мы уже решили, там будет находиться шаблон для вывода результата вычислений.

Вот пример кода, который получился у меня:

<?php
$result = require __DIR__ . '/calc.php';
?>
<html>
<head>
    <title>Калькулятор</title>
</head>
<body>
    <b>Результат вычислений:</b>
    <br>
    <?= $result ?>
</body>
</html>

Как видим, здесь всё предельно просто — в переменную $result присваивается значение, возвращаемое из файла calc.php. Затем мы просто-напросто выводим результат из этой переменной.

Бизнес-логика калькулятора

Теперь самое интересное — написать бизнес-логику. Создаём файл calc.php и начинаем думать.

Первое, в чём нам стоит убедиться, есть ли вообще какие-либо данные в GET-запросе. Для этого проверяем массив $_GET на пустоту:

<?php
if (empty($_GET)) {
    return 'Ничего не передано!';
}

Сейчас, если перейти по адресу http://myproject.loc/result.php, мы увидим соответствующий результат:
Исходные данные пусты

Далее, нам стоит проверить, что из формы переданы x1, x2 и operation.

<?php
if (empty($_GET)) {
    return 'Ничего не передано!';
}

if (empty($_GET['operation'])) {
    return 'Не передана операция';
}

if (empty($_GET['x1']) || empty($_GET['x2'])) {
    return 'Не переданы аргументы';
}

Можно теперь вернуться на форму с исходными данными и заполнить её какими-нибудь данными:
Заполняем форму исходными данными

Если теперь нажать на кнопку отправки формы, мы увидим, что никаких ошибок в форме результата теперь не возникло:
Результат без ошибок

Вместо этого мы теперь видим число 1. Это результат того, что в файле calc.php мы ничего не вернули, но при этом попытались это «ничего» с помощью функции require присвоить в переменную $result. Единица вернулась нам, потому что файл был успешно подключен, но ничего не вернул. Это значение по умолчанию.

Попробуем теперь убрать один из аргументов в форме:
Убираем аргумент из исходных данных

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

Ну что, теперь мы знаем, что данные у нас проверяются, можно с ними и поработать.

Давайте для удобства сделаем 2 переменные $x1 и $x2 и положим в них значения из GET-запроса.

//… продолжение файла
$x1 = $_GET['x1'];
$x2 = $_GET['x2'];

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

//… продолжение файла
$x1 = $_GET['x1'];
$x2 = $_GET['x2'];

$expression = $x1 . ' ' . $_GET['operation'] . ' ' . $x2 . ' = ';
return $expression;

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

Дело осталось за малым — нужно только посчитать результат.

Для того, чтобы определить, какое действие нужно совершить с аргументами, воспользуемся конструкцией switch-case.

В результате получаем такое содержимое calc.php.

<?php
if (empty($_GET)) {
    return 'Ничего не передано!';
}

if (empty($_GET['operation'])) {
    return 'Не передана операция';
}

if (empty($_GET['x1']) || empty($_GET['x2'])) {
    return 'Не переданы аргументы';
}

$x1 = $_GET['x1'];
$x2 = $_GET['x2'];

$expression = $x1 . ' ' . $_GET['operation'] . ' ' . $x2 . ' = ';

switch ($_GET['operation']) {
    case '+':
        $result = $x1 + $x2;
        break;
    case '-':
        $result = $x1 - $x2;
        break;
    default:
        return 'Операция не поддерживается';
}

return $expression . $result;

Давайте теперь снова отправим форму и посмотрим на результат.
Калькулятор готов

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

Домашнее задание

  1. Попробуйте в качестве одного из аргументов передать 0. Какой получился результат? Почему так? Дополните код так, чтобы можно было передавать 0.
  2. Усовершенствуйте калькулятор так, чтобы он умножал и делил.
  3. Что произойдёт, если поделить на ноль? Добавьте обработку такой ситуации.
  4. Что произойдёт, если в качестве аргумента передать вместо числа строку? Сделайте так, чтобы в качестве аргументов можно было отправить только числа.
  5. Какие ещё недостатки есть у этого кода? Как можно нарушить его работу? Что можно улучшить?

В комментариях пишите только содержимое файла calc.php . Код из файлов index.php и result.php приводить не нужно.

Учимся работать с cookie в PHP

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

Для чего нужны cookie

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

В качестве решения придумали cookie. Это такие записи с типом ключ-значение, типа массива в PHP, только хранятся они в браузере у пользователя сайта. Для каждого сайта cookie хранятся отдельно. Каждый раз, когда пользователь обращается с запросом на сайт, браузер проверяет наличие этих записей для данного сайта. И если они имеются, то он отправляет их в заголовке каждого запроса к этому сайту.

Откуда берутся cookie

Cookie создаются в браузере по «просьбе» сервера. В какой-то момент мы решаем, что нужно в браузере посетителя создать cookie с каким-то значением. Для этого нужно чтобы сервер передал в ответе клиенту специальный заголовок, в котором указано, какую запись нужно создать в браузере для данного сайта.

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

То есть сервер примерно говорит: «Эй, браузер, создай запись для меня с ключом “login” и значением “admin”, и ещё одну с ключом “password” и значением “123”». После этого браузер при любом запросе к серверу начинает отправлять дополнительные данные типа:

login: admin
password: 123

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

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

Про время жизни

При этом у cookie есть TTL (Time To Live – время жизни). То есть эти записи могут быть временными. Это время жизни так же указывается сервером во время установки cookie в браузер. Благодаря этому можно сделать так, чтобы сессия длилась пол часа. А после этого времени пользователю надо будет авторизоваться снова. Наверняка вы замечали это в действии на многих сайтах.

Как работать с cookie в PHP

Итак, мы в общих чертах разобрались с тем, как работают cookie. Давайте теперь посмотрим, как с ними можно работать в языке PHP.

Давайте создадим в нашем проекте файл с именем viewCookies.php. Поместим в него следующий код.

<?php
var_dump($_COOKIE);

Как вы уже должны были догадаться, $_COOKIE — это еще один глобальный массив в PHP, аналогично массивам $_GET и $_POST . Только в нём хранятся все cookie, которые были отправлены браузером в рамках текущего запроса.

Давайте посмотрим на работу данного скрипта, открыв в браузере страницу: http://myproject.loc/viewCookies.php

Вывод массива $_COOKIE

Как мы видим, в данный момент этот массив пуст. Давайте же его наполним :slight_smile: Для этого нам нужно установить какую-нибудь cookie в браузер. В PHP для этого используется функция setcookie($name, $value, $ttl, $path)

Передаваемые параметры:

  • $name – название cookie
  • $value – её значение
  • $ttl – время жизни. Если указать 0, то cookie будет установлена навсегда (пока её не удалят).
  • $path – путь в адресной строке. Если задать ‘/’, cookie будут доступны из всех директорий сайта. Например, и в http://myproject.loc/ и в http://myproject.loc/posts/. Если задать ‘/posts/’, cookie будут доступны только из директории http://myproject.loc/posts/ и всех ее поддиректорий (например, http://myproject.loc/posts/new/). По умолчанию значением является текущая директория, в которой cookie устанавливается. Если мы хотим, чтобы cookie была доступна на всём сайте, то нужно устанавливать это значение в ‘/’.

Есть еще несколько параметров, о них вы можете прочитать в официальной документации.

А теперь давайте попробуем эту функцию в деле. Создадим файл setCookies.php и запишем в него следующий код:

<?php
setcookie('login', 'admin', 0, '/');

После этого перейдём по адресу http://myproject.loc/setCookies.php, где увидим пустую страницу. Как мы уже говорили, работа с cookie не видна пользователю.

Однако, эту работу всегда можно увидеть в консоли разработчика Google Chrome. Давайте откроем её (нажатием F12), перейдём во вкладку Network, обновим страницу в браузере и найдём её в списке загруженных данных (она там одна).

Список запросов в консоли разработчика Google Chrome

Нажмем на эту запись и в открывшемся справа окне выберем вкладку Headers. Здесь, в секции Response Headers мы можем видеть заголовок Set-Cookie с указанными нами данными.

Смотрим заголовки ответа

Таким образом, cookie были успешно установлены в браузере. Давайте теперь перейдём на нашу страничку, выводящую массив $_COOKIE - http://myproject.loc/viewCookies.php

Вывод cookie из массива

Как мы видим, теперь на сервер передаются cookie, ранее установленные в браузере. Увидеть их можно и в запросе, посмотрев в консоли разработчика секцию Request Headers.

Заголовки запроса

Если вам нужно посмотреть все cookie, которые имеются в браузере для данного сайта — можно посмотреть их в той же консоли разработчика Google Chrome. Для этого перейдем во вкладку Application, выберем в левом списке пункт Cookies, а внутри него наш сайт.

Список всех cookie для сайта в консоли разработчика

Все cookie будут представлены в виде удобного списка.

Что еще нужно знать про cookie

И в заключение данного урока нужно добавить, что cookie устанавливаются с помощью заголовка в ответе сервера по протоколу HTTP. Протокол HTTP устроен таким образом, что заголовок должен всегда идти перед данными, и никак иначе. Таким образом, функция setcookie и любые другие функции в PHP, изменяющие заголовок в HTTP-ответе, должны вызываться до любого вывода данных.

Можно сначала задать cookie, а затем вывести текст.

<?php
setcookie('login', 'admin', 0, '/');
echo 'Cookie установлены';

Сначала заголовок а потом тело

Всё прекрасно отработает.

Но нельзя вывести текст (являющийся телом HTTP-ответа), а затем пытаться установить cookie.

<?php
echo 'Сейчас попробуем установить cookie';
setcookie('login', 'admin', 0, '/');

Ошибка при попытке изменить заголовок после начала передачи тела

Как мы видим, это приведет к ошибке. Так устроен протокол HTTP. Сначала — заголовок, затем — тело. Только так и никак иначе.

Установка нескольких cookie

Нет ничего проще, чем установить несколько cookie. Для этого нужно просто несколько раз вызвать функцию setcookie.

<?php
setcookie('login', 'admin', 0, '/');
setcookie('password', 'p@SsW0rd', 0, '/');
echo 'Cookie установлены';

Установка нескольких cookie в браузер

Они успешно будут переданы клиенту.

Пример полноценного взаимодействия с пользователем через cookie мы рассмотрим в следующем уроке. А пока — за домашку.

Домашнее задание

Укажите параметр $ttl в функции setcookie равным 20.

Запустите этот скрипт и посмотрите, что изменилось в заголовке Set-Cookie в консоли разработчика.

Сразу после установки cookie перейдите на страничку http://myproject.loc/viewCookies.php и убедитесь что эта cookie сейчас содержится в массиве и выводится.

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

Система авторизации с помощью cookie на PHP

В прошлом уроке мы изучили механизм взаимодействия с cookie в языке PHP. Теперь давайте попробуем применить эти знания на практике — создадим простейшую систему авторизации.

Техническое задание

Начнём мы это дело с описания будущей системы. Пусть у нас будут следующие компоненты:

  1. Главная страница сайта с каким-либо содержимым. Вверху страницы выводится:
  • если пользователь авторизован: Добро пожаловать, %username%.
  • если пользователь неавторизован: Авторизация — слово является ссылкой, которая ведёт на форму авторизации.
    Авторизован пользователь или нет, определяется с помощью cookie.
  1. Страница с формой авторизации. Два инпута для логина и пароля и кнопкой «Вход». Если введены правильные логин и пароль, устанавливаются cookie со значениями переданных данных, а затем пользователя автоматически редиректит (перенаправляет) на главную страницу.
  2. Страница для разлогинивания — при переходе на неё cookie будут удаляться из браузера пользователя, а затем выполняется редирект на главную страницу.

Решение

Продумываем архитектуру

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

Начнем с простого — для начала у нас должно получиться 3 странички, которые мы описали в ТЗ.

Ещё нам потребуется функционал, который будет проверять, авторизован ли пользователь. Если мы перечитаем ТЗ, то поймём, что он используется в двух местах — и на главной странице и на странице авторизации. Значит, стоит вынести этот механизм в отдельный файл, и использовать его в двух местах сразу.

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

Пишем код

Все исходники по данному заданию доступны здесь.

База данных

Ну вот, всё продумали, осталось только написать. Предлагаю начать с нашей базы данных. Создадим файл usersDB.php и запишем в него несколько пользователей.

<?php
return [
    ['login' => 'admin', 'password' => 'P@ssw0rd'],
    ['login' => 'moderator', 'password' => 'password'],
    ['login' => 'user', 'password' => '123'],
];

Функции проверки авторизации

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

<?php
function checkAuth(string $login, string $password): bool 
{
    $users = require __DIR__ . '/usersDB.php';

    foreach ($users as $user) {
        if ($user['login'] === $login 
            && $user['password'] === $password
        ) {
            return true;
        }
    }

    return false;
}

В цикле мы пробегаемся по базе данных пользователей и пытаемся найти пользователя с переданными логином и паролем. Если такой пользователь в массиве найден — возвращаем true. Иначе — false.

Давайте теперь ещё напишем функцию, которая будет возвращать логин текущего пользователя. Эта функция будет проверять текущие значения cookie с ключами login и password с помощью уже существующей функции checkAuth. При этом если пользователь найдётся, то она вернёт его login, а иначе — null. Назовём эту нашу новую функцию getUserLogin.

//продолжение файла auth.php

function getUserLogin(): ?string
{
    $loginFromCookie = $_COOKIE['login'] ?? '';
    $passwordFromCookie = $_COOKIE['password'] ?? '';

    if (checkAuth($loginFromCookie, $passwordFromCookie)) {
        return $loginFromCookie;
    }

    return null;
}

На этом всю логику проверки логина мы описали. Теперь займёмся непосредственно страничками.

Главная страница

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

<?php
require __DIR__ . '/auth.php';
$login = getUserLogin();
?>
<html>
<head>
    <title>Главная страница</title>
</head>
<body>
<?php if ($login === null): ?>
<a href="/login.php">Авторизуйтесь</a>
<?php else: ?>
Добро пожаловать, <?= $login ?>
<br>
<a href="/logout.php">Выйти</a>
<?php endif; ?>
</body>
</html>

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

Главная страничка с призывом авторизации

Форма авторизации

Давайте теперь сделаем форму авторизации — создаём файл login.php и для начала набрасываем саму HTML-форму. Шаблон получился следующим.

<html>
<head>
    <title>Форма авторизации</title>
</head>
<body>
<form action="/login.php" method="post">
    <label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
    <br>
    <label for="password">Пароль: </label><input type="password" name="password" id="password">
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

Давайте теперь добавим логику проверки переданных данных.

<?php
if (!empty($_POST)) {
    require __DIR__ . '/auth.php';

    $login = $_POST['login'] ?? '';
    $password = $_POST['password'] ?? '';

    if (checkAuth($login, $password)) {
        setcookie('login', $login, 0, '/');
        setcookie('password', $password, 0, '/');
        header('Location: /index.php');
    } else {
        $error = 'Ошибка авторизации';
    }
}
?>
<html>
<head>
    <title>Форма авторизации</title>
</head>
<body>
<?php if (isset($error)): ?>
<span style="color: red;">
    <?= $error ?>
</span>
<?php endif; ?>
<form action="/login.php" method="post">
    <label for="login">Имя пользователя: </label><input type="text" name="login" id="login">
    <br>
    <label for="password">Пароль: </label><input type="password" name="password" id="password">
    <br>
    <input type="submit" value="Войти">
</form>
</body>
</html>

Логика простейшая — если был отправлен POST-запрос, проверяем правильные ли логин и пароль были переданы.

Если нет — то создаём переменную $error, в которой пишем об ошибке авторизации. Позже в шаблоне выводим эту ошибку, если эта переменная объявлена.

Если же авторизация прошла успешно, мы устанавливаем cookie с ключами login и password, в которые помещаем значения из POST-запроса. После этого выполняем редирект на главную страницу.

Редирект делается с помощью заголовка в HTTP-ответе. Этот заголовок называется Location и выглядит следующим образом:

Location: адрес_на_который_нужно_перейти

Для формирования заголовков в PHP используется функция header – ознакомиться с ней более детально вы можете здесь.

Теперь можно попробовать нашу страничку в действии. Давайте для начала введём несуществующую пару логина и пароля. Например, 123:123.

Ошибка при авторизации

Мы увидим соответствующую ошибку.

Теперь давайте зайдем под пользователем user. В нашей БД для него указан пароль 123. Пробуем…

Успешная авторизация

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

Безопасная система авторизации

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

Хеширование паролей

В более совершенных системах авторизации используют хеш от пароля.

Если по-простому, то это такое вычисленное значение, полученное в результате выполнения над паролем определенных манипуляций. В результате этих действий мы получаем строку, из которой нельзя восстановить исходный пароль.

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

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

  1. злоумышленник не сможет использовать пароль для входа на взломанный сайт;
  2. он также не сможет использовать этот пароль для входа под тем же логином и паролем в другие места (ведь довольно часто люди используют одинаковые пароли для всего).

Вычисляются хеши с помощью хеш-функции. Хеш-функции при этом вычисляют хеши следуя алгоритмам хеширования. Сейчас в PHP для хеширования следует использовать функцию password_hash(), а для проверки хеша - password_verify(). Если вы в каком-то уроке увидите, что для хеширования паролей используется md5 - бегите оттуда, такие хеши вскрываются за несколько минут, она устарела ещё лет 10 назад.

Авторизационные токены

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

И потом пользователь с помощью cookie передает этот токен на сервер, где он сравнивается со значением в базе данных. Если они равны, то считаем пользователя авторизованным. Для чего? Дело в том, что куки могут быть похищены злоумышленниками (очень многими способами, не будем об этом в этой статье, кому интересно - погуглите). И если злоумышленнику попадет в руки токен - он не сможет получить исходный пароль. О том, почему это так важно, я уже объяснил.

Заключение

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

Ах да, чуть не забыл, все исходники к каждому уроку я для вашего удобства буду выкладывать на github – вот тут.

Домашнее задание

  • Создайте страницу для разлогинивания - logout.php. При переходе на неё должны удаляться cookie с ключами login и password и выполняться редирект на главную страницу. В качестве ответа предоставьте полный код файла logout.php.
  • Сейчас при переходе авторизованного пользователя на страницу login.php открывается форма авторизации. Сделайте предварительную проверку того, что пользователь уже авторизован. И если он является авторизованным, перенаправляйте его на главную страницу. В качестве ответа на это задание предоставьте только код, который вы добавили в файл login.php.

Учимся работать с файлами в PHP

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

Построчное чтение из файла

Для начала давайте научимся читать файлы. Давайте создадим текстовый файл file.txt и запишем в него следующий текст:

В век высоких технологий
Без программ не обойтись,
Программисты ежедневно
Легче делают нам жизнь! 

Давайте теперь считаем его с помощью программы на языке php. Создайте файл files.php и запишите в него следующий код:

<?php
$file = fopen(__DIR__ . '/file.txt', 'r');
for ($i = 0; $i < 4; $i++) {
    echo fgets($file);
    echo '<br>';
}
fclose($file);

Если вы сейчас запустите этот скрипт, то увидите на экране четверостишие из файла.

Построчное чтение файла на PHP

Давайте теперь разберём по шагам нашу программу.

  1. Функция fopen открывает файл. Первым аргументом указывается путь до файла, вторым аргументом указывается режим для работы с файлом. В нашем случае это ‘r’ – чтение (от read). Ознакомьтесь с другими режимами работы в документации по функции. Данная функция возвращает ресурс — это еще один тип данных, с которым мы ранее не работали. Ресурс в PHP – это ссылка на какой-то внешний ресурс (файл, база данных и т. п.). Работа с ресурсами происходит с помощью специальных функций. Более детально о ресурсах вы можете прочитать тут.
  2. В цикле for четыре раза вызывается функция fgets. Эта функция считывает строку из файла и перемещает текущий курсор на следующую строку (курсор можете представить себе как обычный курсор в текстовом редакторе, который просто перемещается в момент считывания данных из файла). Если снова вызвать эту функцию, она сделает то же самое для следующей строки. Возвращает эта функция считанную строку (string).
  3. Ресурс закрывается с помощью функции fclose. Делать это следует всегда, несмотря на то, что PHP по завершении работы программы сделает это сам. Дело в том, что если открыть файл в блокирующем режиме (например, для записи, в режиме ‘w’), то он будет недоступен для других процессов, они будут ждать, пока предыдущий ресурс освободит этот файл.

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

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

<?php
$file = fopen(__DIR__ . '/file.txt', 'r');
while (!feof($file)) {
    echo fgets($file);
    echo '<br>';
}
fclose($file);

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

Построчная запись в файл

Теперь запишем данные в файл. Для того чтобы открыть файл для записи используется та же функция fopen, только в качестве режима работы указывается ‘w’ (от write).

А для записи в файл строки используется функция fputs(). Первым аргументом указывается ресурс, а вторым — строка, которую необходимо записать в файл. Давайте в качестве примера напишем программу, которая запишет в файл file2.txt числа от 1 до 100.

<?php
$file = fopen(__DIR__ . '/file2.txt', 'w');
for ($i = 1; $i < 101; $i++) {
    fputs($file, $i . PHP_EOL);
}
fclose($file);

Константа PHP_EOL содержит в себе символ переноса строки. При этом для разных операционных систем (Windows или Unix-подобных) эти символы будут разными.

Дозаписываем в конец файла

При этом если сейчас запустить программу снова, то старые данные в файле file2.txt перезапишутся новыми. Для того, чтобы сохранить содержимое файла и дозаписать данные в конец, нужно использовать режим работы с файлом “a” (от append – присоединять, добавлять).

<?php
$file = fopen(__DIR__ . '/file3.txt', 'a');
fputs($file, 'abc' . PHP_EOL);
fclose($file);

Если мы запустим этот скипт дважды, то в файле file3.txt будет две строки “abc”.

Читаем файл целиком

В PHP также имеется возможность прочитать весь файл за раз. Для этого используется функция file_get_contents().

<?php
$data = file_get_contents(__DIR__ . '/file.txt');
echo $data;

Данный код выведет всё то же четверостишие, только без переноса строк.

Чтение файла целиком на PHP

Так произошло потому, что в самом первом примере мы добавляли тег <br> после каждой прочитанной строки. Если же мы откроем исходный код страницы, то увидим, что на самом деле переносы строк в исходном коде сохранены.

Исходник прочитанного файла

Запись в файл данных целиком

Для того, чтобы записать в файл большой объем данных за раз — сразу несколько строк, можно воспользоваться функцией file_put_contents.

<?php
$data = 'abc' . PHP_EOL . 'def' . PHP_EOL;
file_put_contents(__DIR__ . '/file3.txt', $data);

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

<?php
$data = 'abc' . PHP_EOL . 'def' . PHP_EOL;
file_put_contents(__DIR__ . '/file3.txt', $data, FILE_APPEND);

Какой способ выбрать

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

Домашнее задание

  1. Напишите программу, которая выводит свой собственный код.
    Сделайте так, чтобы в этой программе не было упоминания имени самого скрипта (если программа лежит в файле homework.php, то упоминания homework.php не должно быть в исходнике).
  2. Выведите список всех файлов в текущей директории скрипта.
    Создайте теперь в директории со скриптом несколько папок.
    Сделайте так, чтобы в списке, выводимом программой, остались только папки.

Загрузка файлов на сервер

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

Для начала давайте обговорим, как будет выглядеть наше мини-приложение. Пусть у нас будет форма для загрузки файлов: upload.php . Этот же файл будет содержать логику по обработке загружаемых пользователем файлов. И ещё давайте создадим папку uploads , в которую будем складывать все загружаемые файлы.

Давайте для начала напишем саму форму для загрузки файла. Она ничем не отличается от обычной формы для POST-запроса. Единственное отличие – появляется input со специальным типом file . У него также как и у текстового input’а есть атрибут name. Простейшая форма для загрузки файла будет выглядеть следующим образом. Содержимое файла upload.php:

<html>
<head>
    <title>Загрузка файла</title>
</head>
<body>
<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="attachment">
    <input type="submit">
</form>
</body>
</html>

Для того, чтобы начать работать с файлом на стороне PHP, нужно познакомиться с ещё одним суперглобальным массивом в PHP - $_FILES . Так как мы указали в input атрибуту name значение attachment , то и узнать информацию о загружаемом нами файле можно получить по соответствующему ключу - $_FILES[‘attachment’] .

Давайте разместим PHP-код для обработки загружаемого файла в этом же файле. Для начала просто посмотрим что у нас там вообще есть с помощью var_dump.

<?php
if (!empty($_FILES)) {
    var_dump($_FILES);
}
?>
<html>
<head>
    <title>Загрузка файла</title>
</head>
<body>
<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="attachment">
    <input type="submit">
</form>
</body>
</html>

Откроем в браузере нашу форму http://myproject.loc/upload.php, и выберем какой-нибудь файл для загрузки. Я для этого выбрал картинку, валяющуюся на рабочем столе.
выбор файла для загрузки

После нажатия на кнопку «Отправить» мы получим содержимое массива $_FILES.
Суперглобальный массив $_FILES

Значение $_FILES[‘attachment’] представляет собой массив с пятью ключами. Давайте рассмотрим каждый из элементов этого массива более подробно.

  • name – имя файла, переданное из браузера;
  • type – тип файла, в моём случае это image/png;
  • tmp_name – путь до временного файла, который загрузился на сервер, но если с ним до конца запроса ничего не сделать, то он удалится;
  • error – код ошибки при загрузке файла, если 0 – то ошибки нет. Изучите возможные коды ошибок вот тут;
  • size – размер загруженного файла в байтах.

Как мы уже сказали, пока что на сервере создан временный файл. Чтобы его сохранить нужно вызвать специальную функцию, которая переместит его из временной папки в нужную нам папку (в нашем случае это папка uploads). Эта функция - move_uploaded_file() . Первым аргументом она принимает путь до временного файла (в нашем случае - $_FILES[‘attachment’][‘tmp_name’]), а вторым аргументом – путь, по которому нужно его сохранить. Функция вернёт true, если всё ок, и false, если что-то пошло не так.

ВНИМАНИЕ! Для перемещения временного файла в нужную вам папку стоит всегда использовать функцию move_uploaded_file(). Она предварительно проверяет, что указанный файл действительно является временным загруженным файлом, а не чем-то ещё. Другие функции для копирования файлов не подойдут, так как они эту проверку не выполняют, а это небезопасно. Кому интересно – можете подробнее прочитать тут.

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

<?php
if (!empty($_FILES['attachment'])) {
    $file = $_FILES['attachment'];

    // собираем путь до нового файла - папка uploads в текущей директории
    // в качестве имени оставляем исходное файла имя во время загрузки в браузере
    $srcFileName = $file['name'];
    $newFilePath = __DIR__ . '/uploads/' . $srcFileName;

    if (!move_uploaded_file($file['tmp_name'], $newFilePath)) {
        $error = 'Ошибка при загрузке файла';
    } else {
        $result = 'http://myproject.loc/uploads/' . $srcFileName;
    }
}
?>
<html>
<head>
    <title>Загрузка файла</title>
</head>
<body>
<?php if (!empty($error)): ?>
    <?= $error ?>
<?php elseif (!empty($result)): ?>
    <?= $result ?>
<?php endif; ?>
<br>
<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="attachment">
    <input type="submit">
</form>
</body>
</html>

Если сейчас выбрать файл и нажать на кнопку “Отправить”, то скрипт вернёт нам адрес загруженного файла.
Результат загрузки файла через PHP

Мы можем открыть этот адрес в новой вкладке, и убедиться, что действительно файл доступен по этому адресу. Ну и если посмотрим в папку uploads, то тоже увидим, что наш файлик теперь там валяется.

Этот скрипт ещё очень сырой, давайте его доработаем.

Проверка на существующий файл с таким же именем

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

<?php
if (!empty($_FILES['attachment'])) {
    $file = $_FILES['attachment'];

    $srcFileName = $file['name'];
    $newFilePath = __DIR__ . '/uploads/' . $srcFileName;

    if (file_exists($newFilePath)) {
        $error = 'Файл с таким именем уже существует';
    } elseif (!move_uploaded_file($file['tmp_name'], $newFilePath)) {
        $error = 'Ошибка при загрузке файла';
    } else {
        $result = 'http://myproject.loc/uploads/' . $srcFileName;
    }
}
?>
...

Обработка ошибок при загрузке файлов

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

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

<?php
if (!empty($_FILES['attachment'])) {
    $file = $_FILES['attachment'];

    $srcFileName = $file['name'];
    $newFilePath = __DIR__ . '/uploads/' . $srcFileName;

    if ($file['error'] !== UPLOAD_ERR_OK) {
        $error = 'Ошибка при загрузке файла.';
    } elseif (file_exists($newFilePath)) {
        $error = 'Файл с таким именем уже существует';
    } elseif (!move_uploaded_file($file['tmp_name'], $newFilePath)) {
        $error = 'Ошибка при загрузке файла';
    } else {
        $result = 'http://myproject.loc/uploads/' . $srcFileName;
    }
}
?>
...

Проверяем расширение загружаемого файла

Ещё необходимо проверить то, что у загружаемого файла корректное расширение. Например, если мы хотим чтобы через форму загружали только картинки, то допустимыми расширениями для нас будут .jpg, .gif, .png.

Если этого не предусмотреть – то через форму загрузки можно будет загрузить какой-нибудь вредоносный скрипт. Представьте, что кто-то загрузил на сервер .php-скрипт, который удаляет все файлы в текущей директории, а затем просто запустил его, зайдя по адресу http://myproject.loc/uploads/very_bad_script.php. И это еще не самое страшное, что могут сделать злобные хакеры. Поэтому нужно всегда контролировать то, что пользователи загружают на сервер.

Получить расширение файла можно при помощи функции pathinfo(). Первым аргументом передаётся путь до файла, вторым – константа PATHINFO_EXTENSION.

$extension = pathinfo($srcFileName, PATHINFO_EXTENSION);

В переменной $extension после это будет строка c расширением загруженного файла, например, png.
Нам остаётся только проверить, что расширение загружаемого файла находится в списке разрешенных. Давайте создадим массив со списком таких расширений и проверим наличие расширения загружаемого файла в списке разрешенных.

$allowedExtensions = ['jpg', 'png', 'gif'];
$extension = pathinfo($srcFileName, PATHINFO_EXTENSION);
if (!in_array($extension, $allowedExtensions)) {
    $error = 'Загрузка файлов с таким расширением запрещена!';
}

Теперь, если вы попробуете загрузить файл с каким-то другим расширением, то скрипт на нас ругнётся.
Ошибка при плохом расширении

Результат

В ходе данного урока получили скрипт следующего содержания.

<?php
if (!empty($_FILES['attachment'])) {
    $file = $_FILES['attachment'];

    $srcFileName = $file['name'];
    $newFilePath = __DIR__ . '/uploads/' . $srcFileName;

    $allowedExtensions = ['jpg', 'png', 'gif'];
    $extension = pathinfo($srcFileName, PATHINFO_EXTENSION);
    if (!in_array($extension, $allowedExtensions)) {
        $error = 'Загрузка файлов с таким расширением запрещена!';
    } elseif ($file['error'] !== UPLOAD_ERR_OK) {
        $error = 'Ошибка при загрузке файла.';
    } elseif (file_exists($newFilePath)) {
        $error = 'Файл с таким именем уже существует';
    } elseif (!move_uploaded_file($file['tmp_name'], $newFilePath)) {
        $error = 'Ошибка при загрузке файла';
    } else {
        $result = 'http://myproject.loc/uploads/' . $srcFileName;
    }
}
?>
<html>
<head>
    <title>Загрузка файла</title>
</head>
<body>
<?php if (!empty($error)): ?>
    <?= $error ?>
<?php elseif (!empty($result)): ?>
    <?= $result ?>
<?php endif; ?>
<br>
<form action="/upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="attachment">
    <input type="submit">
</form>
</body>
</html>

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

Домашнее задание

  • Позвольте загружать только файлы размером меньше 8Мб. Сделайте это с помощью сравнения с $_FILES[‘attachment’][‘size’].
  • Изучите директиву upload_max_filesize в файле php.ini. Установите её значение, равное 2M. Перезапустите веб-сервер. Попробуйте теперь загрузить файл, размером в 5Мб. Теперь обработайте соответствующую ошибку с помощью проверки значения $_FILES[‘attachment’][‘error’].
  • Разрешите загружать картинки с шириной не более 1280px и высотой не более 720px.

Пишем фотоальбом на PHP

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

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

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

Создадим также файл upload.php из прошлого урока и папку upload. Должно получиться вот так.

Загружать картинки может только авторизованный пользователь

Теперь давайте сделаем так, чтобы upload.php мог использовать только авторизованный пользователь.
Для этого в начале скрипта добавим проверку того, что пользователь авторизован:

<?php

require __DIR__ . '/auth.php';
$login = getUserLogin();

if ($login !== null && !empty($_FILES['attachment'])) {
...

Этим мы просто не допустим загрузку файла на сервер. Помимо этого в upload.php нужно неавторизованному пользователю вообще не показывать форму для загрузки файлов. Проще простого, добавляем ещё одно условие:

...
<html>
<head>
    <title>Загрузка файла</title>
</head>
<body>
<?php if ($login === null): ?>
    <a href="/login.php">Авторизуйтесь</a>
<?php else: ?>
    Добро пожаловать, <?= $login ?> |
    <a href="/logout.php">Выйти</a>
    <br>
    <?php if (!empty($error)): ?>
        <?= $error ?>
    <?php elseif (!empty($result)): ?>
        <?= $result ?>
    <?php endif; ?>
    <br>
    <form action="/upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="attachment">
        <input type="submit">
    </form>
<?php endif; ?>
</body>
</html>

Отлично, теперь загружать файлы можно только после логина.

Список фотографий

Давайте теперь сделаем страничку с выводом списка фотографий. Для этого мы изменим файл index.php.

Для начала давайте загрузим с помощью upload.php несколько фоток. Я взял картинки с котиками, назвал их 1.jpg, 2.jpg, 3.jpg и загрузил их через формочку. После этого они появились в папке uploads.

Фотки после загрузки на сервер

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

Давайте запишем в файл index.php следующий код:

<?php
var_dump(scandir(__DIR__ . '/uploads'));

Теперь запустим скрипт в браузере и увидим следующее:

Файлы в папке uploads

. и – это текущая директория и родительская директория соответственно. А вот всё остальное – это наши файлы.

Давайте сформируем список ссылок на наши картинки. Пройдёмся по массиву с именами файлов и приклеим перед их именами текст http://myproject.loc/uploads/, а ещё нам нужно проигнорировать папки . и .

$files = scandir(__DIR__ . '/uploads');
$links = [];
foreach ($files as $fileName) {
    if ($fileName === '.' || $fileName === '..') {
        continue;
    }
    $links[] = 'http://myproject.loc/uploads/' . $fileName;
}
var_dump($links);

Теперь в $links лежат прямые ссылки на все наши загруженные картинки.

Ссылки на загруженные картинки

Остаётся только вывести превьюшки!

<html>
<head>
    <title>Фотоальбом</title>
</head>
<body>
<?php
$files = scandir(__DIR__ . '/uploads');
$links = [];
foreach ($files as $fileName) {
    if ($fileName === '.' || $fileName === '..') {
        continue;
    }
    $links[] = 'http://myproject.loc/uploads/' . $fileName;
}

foreach ($links as $link):?>
    <img src="<?= $link ?>" height="80px">
<?php endforeach; ?>
</body>
</html>

Полюбуемся на результат.

Вывод фоток в альбоме

Остаётся только добавить ссылки, чтобы при клике на картинки открывалась исходная фоточка.

...
foreach ($links as $link):?>
    <a href="<?= $link ?>"><img src="<?= $link ?>" height="80px"></a>
<?php endforeach; ?>
...

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

1 симпатия